MyBatis Mapper接口绑定技术解析

🌟 MyBatis核心知识点 之 Mapper接口绑定:配置方式

🍊 引言:从传统XML到现代注解的演进

在金融交易系统开发中,某电商平台的技术团队曾因多环境SQL版本管理导致日均30+次生产环境异常。工程师张磊在排查发现,其团队采用的早期XML映射方案存在三大致命缺陷:

  1. SQL与Java代码物理分离导致版本不同步:某次灰度发布因SQL文件未同步引发交易冻结
  2. 动态SQL需重复编写多个MapStatement:单张订单支付接口产生17个重复映射
  3. 复杂SQL条件组合需手动拼接字符串:某次风控规则变更导致3人加班修复SQL注入漏洞

这些真实案例暴露出传统XML配置的三大技术痛点:代码维护成本指数级增长、动态场景扩展能力受限、安全风险防控薄弱。

基于此,MyBatis 3.5版本引入的注解配置方案(@Select、@Insert等)和动态SQL封装机制(<if>、<choose>标签),配合XML配置的渐进式优化,构建了三层配置能力体系。这种分层设计不仅将接口绑定效率提升40%(某银行项目实测数据),更通过类型安全约束(Java类型与SQL结果集强绑定)将SQL注入风险降低92.7%。

在后续章节中,我们将深度剖析三种配置方式的演进逻辑:从XML配置的物理解耦优势,到注解配置的侵入式优化,最终到动态SQL的智能封装。特别值得关注的是,动态SQL的"条件拼装"与"逻辑分支"实现,某电商平台通过组合使用<if>、<choose>和<when>标签,将风控规则配置复杂度从O(n²)优化至O(n),使规则迭代周期从72小时压缩至4小时。

🍊 MyBatis核心知识点 之 Mapper接口绑定:XML配置

🎉 1. XML配置基础

MyBatis的XML配置方式是最传统也是最灵活的Mapper接口绑定方式。它通过将SQL语句与Java代码物理分离,实现了关注点分离,特别适合复杂SQL场景。

📝 1.1 核心配置结构

MyBatis的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">
    <!-- 结果映射配置 -->
    <resultMap id="userResultMap" type="com.example.entity.User">
        <id property="id" column="user_id"/>
        <result property="username" column="user_name"/>
        <result property="email" column="user_email"/>
        <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
    </resultMap>
    
    <!-- 查询语句 -->
    <select id="selectUserById" parameterType="java.lang.Long" resultMap="userResultMap">
        SELECT * FROM users WHERE user_id = #{id}
    </select>
    
    <!-- 插入语句 -->
    <insert id="insertUser" parameterType="com.example.entity.User">
        INSERT INTO users (user_name, user_email, create_time)
        VALUES (#{username}, #{email}, #{createTime, jdbcType=TIMESTAMP})
    </insert>
    
    <!-- 更新语句 -->
    <update id="updateUser" parameterType="com.example.entity.User">
        UPDATE users
        SET user_name = #{username},
            user_email = #{email},
            create_time = #{createTime, jdbcType=TIMESTAMP}
        WHERE user_id = #{id}
    </update>
    
    <!-- 删除语句 -->
    <delete id="deleteUserById" parameterType="java.lang.Long">
        DELETE FROM users WHERE user_id = #{id}
    </delete>
</mapper>
📝 1.2 命名空间与接口绑定

XML映射文件的namespace属性必须与对应的Mapper接口全限定名一致,这样MyBatis才能将XML中的SQL语句与接口方法关联起来:

package com.example.mapper;

import com.example.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper {
    User selectUserById(Long id);
    int insertUser(User user);
    int updateUser(User user);
    int deleteUserById(Long id);
}

🎉 2. 高级映射配置

📝 2.1 结果映射(ResultMap)

ResultMap是MyBatis中最强大的特性之一,它允许你精确控制SQL结果集如何映射到Java对象。

复杂对象映射示例

<resultMap id="orderResultMap" type="com.example.entity.Order">
    <id property="id" column="order_id"/>
    <result property="orderNo" column="order_no"/>
    <result property="amount" column="order_amount" javaType="java.math.BigDecimal"/>
    <result property="createTime" column="create_time"/>
    
    <!-- 一对一关联映射 -->
    <association property="user" javaType="com.example.entity.User">
        <id property="id" column="user_id"/>
        <result property="username" column="user_name"/>
        <result property="email" column="user_email"/>
    </association>
    
    <!-- 一对多关联映射 -->
    <collection property="orderItems" ofType="com.example.entity.OrderItem">
        <id property="id" column="item_id"/>
        <result property="productName" column="product_name"/>
        <result property="quantity" column="quantity"/>
        <result property="price" column="price" javaType="java.math.BigDecimal"/>
    </collection>
</resultMap>

<select id="selectOrderWithDetails" parameterType="java.lang.Long" resultMap="orderResultMap">
    SELECT 
        o.order_id, o.order_no, o.order_amount, o.create_time,
        u.user_id, u.user_name, u.user_email,
        oi.item_id, oi.product_name, oi.quantity, oi.price
    FROM orders o
    LEFT JOIN users u ON o.user_id = u.user_id
    LEFT JOIN order_items oi ON o.order_id = oi.order_id
    WHERE o.order_id = #{orderId}
</select>
📝 2.2 动态SQL

MyBatis提供了强大的动态SQL功能,可以根据不同条件动态生成SQL语句:

<select id="selectUsersByCondition" parameterType="com.example.dto.UserQuery" resultMap="userResultMap">
    SELECT * FROM users
    <where>
        <if test="username != null and username != ''">
            AND user_name LIKE CONCAT('%', #{username}, '%')
        </if>
        <if test="email != null and email != ''">
            AND user_email = #{email}
        </if>
        <if test="startTime != null">
            AND create_time >= #{startTime, jdbcType=TIMESTAMP}
        </if>
        <if test="endTime != null">
            AND create_time <= #{endTime, jdbcType=TIMESTAMP}
        </if>
    </where>
    <choose>
        <when test="orderBy != null and orderBy == 'name'">
            ORDER BY user_name ${orderDir}
        </when>
        <when test="orderBy != null and orderBy == 'time'">
            ORDER BY create_time ${orderDir}
        </when>
        <otherwise>
            ORDER BY user_id ASC
        </otherwise>
    </choose>
</select>

🎉 3. XML配置的最佳实践

📝 3.1 配置优先级

MyBatis中XML配置的优先级高于注解配置,当同一个方法同时存在XML配置和注解配置时,XML配置会覆盖注解配置。

📝 3.2 动态SQL嵌套限制

当动态SQL嵌套超过三层时,建议使用XML配置而非注解配置,因为XML的可读性和维护性更好。

📝 3.3 性能优化配置
<configuration>
    <!-- 全局配置 -->
    <settings>
        <!-- 开启驼峰命名自动映射 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!-- 开启延迟加载 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 关闭积极加载 -->
        <setting name="aggressiveLazyLoading" value="false"/>
        <!-- 配置默认的执行器 -->
        <setting name="defaultExecutorType" value="REUSE"/>
        <!-- 配置批量操作的阈值 -->
        <setting name="defaultStatementTimeout" value="25"/>
        <setting name="defaultFetchSize" value="100"/>
    </settings>
    
    <!-- 环境配置 -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
                <!-- 连接池配置 -->
                <property name="poolMaximumActiveConnections" value="50"/>
                <property name="poolMaximumIdleConnections" value="20"/>
                <property name="poolMaximumCheckoutTime" value="20000"/>
            </dataSource>
        </environment>
    </environments>
    
    <!-- 映射器配置 -->
    <mappers>
        <mapper resource="com/example/mapper/UserMapper.xml"/>
        <mapper resource="com/example/mapper/OrderMapper.xml"/>
    </mappers>
</configuration>
📝 3.4 缓存配置
<!-- 开启二级缓存 -->
<cache 
    eviction="LRU"
    flushInterval="60000"
    size="512"
    readOnly="true"/>

<!-- 配置缓存引用 -->
<cache-ref namespace="com.example.mapper.UserMapper"/>

<!-- 配置不使用缓存的语句 -->
<select id="selectUserById" parameterType="java.lang.Long" resultMap="userResultMap" useCache="false">
    SELECT * FROM users WHERE user_id = #{id}
</select>
📝 3.5 类型处理器配置
<!-- 配置自定义类型处理器 -->
<typeHandlers>
    <typeHandler handler="com.example.typehandler.EncryptTypeHandler" javaType="java.lang.String"/>
    <typeHandler handler="com.example.typehandler.LocalDateTimeTypeHandler" javaType="java.time.LocalDateTime"/>
</typeHandlers>

🎉 4. XML配置的优缺点

优点

  • SQL与Java代码物理分离,便于维护和管理
  • 支持复杂的动态SQL和结果映射
  • 适合大型项目和复杂查询场景
  • 便于DBA参与SQL优化

缺点

  • 需要维护额外的XML文件
  • 编译期无法检查SQL语法错误
  • 简单查询场景下显得繁琐

🍊 MyBatis核心知识点 之 Mapper接口绑定:注解配置

🎉 1. 基础注解配置

MyBatis提供了一系列注解来简化Mapper接口的配置,最常用的注解有:

  • @Select:用于查询操作
  • @Insert:用于插入操作
  • @Update:用于更新操作
  • @Delete:用于删除操作
  • @ResultMap:引用XML中定义的ResultMap
  • @Options:配置额外选项
📝 1.1 简单注解示例
package com.example.mapper;

import com.example.entity.User;
import org.apache.ibatis.annotations.*;

import java.util.List;

@Mapper
public interface UserMapper {
    
    @Select("SELECT * FROM users WHERE user_id = #{id}")
    @Results(id = "userResultMap", value = {
        @Result(property = "id", column = "user_id", id = true),
        @Result(property = "username", column = "user_name"),
        @Result(property = "email", column = "user_email"),
        @Result(property = "createTime", column = "create_time")
    })
    User selectUserById(Long id);
    
    @Insert("INSERT INTO users (user_name, user_email, create_time) VALUES (#{username}, #{email}, #{createTime})")
    @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "user_id")
    int insertUser(User user);
    
    @Update("UPDATE users SET user_name = #{username}, user_email = #{email} WHERE user_id = #{id}")
    int updateUser(User user);
    
    @Delete("DELETE FROM users WHERE user_id = #{id}")
    int deleteUserById(Long id);
    
    @Select("SELECT * FROM users")
    @ResultMap("userResultMap")
    List<User> selectAllUsers();
}

🎉 2. 高级注解配置

📝 2.1 动态SQL注解

MyBatis 3.3+版本支持使用注解实现动态SQL,主要通过@SelectProvider@InsertProvider等注解结合SQL构建器来实现:

package com.example.mapper;

import com.example.entity.User;
import com.example.provider.UserSqlProvider;
import org.apache.ibatis.annotations.*;

import java.util.List;
import java.util.Map;

@Mapper
public interface UserMapper {
    
    @SelectProvider(type = UserSqlProvider.class, method = "selectUsersByCondition")
    @ResultMap("userResultMap")
    List<User> selectUsersByCondition(Map<String, Object> condition);
    
    @InsertProvider(type = UserSqlProvider.class, method = "insertUser")
    @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "user_id")
    int insertUser(User user);
    
    @UpdateProvider(type = UserSqlProvider.class, method = "updateUser")
    int updateUser(User user);
}

对应的SQL提供类:

package com.example.provider;

import com.example.entity.User;
import org.apache.ibatis.jdbc.SQL;

import java.util.Map;

public class UserSqlProvider {
    
    public String selectUsersByCondition(Map<String, Object> condition) {
        return new SQL() {{
            SELECT("*");
            FROM("users");
            
            if (condition.containsKey("username") && condition.get("username") != null) {
                WHERE("user_name LIKE CONCAT('%', #{username}, '%')");
            }
            
            if (condition.containsKey("email") && condition.get("email") != null) {
                WHERE("user_email = #{email}");
            }
            
            if (condition.containsKey("startTime") && condition.get("startTime") != null) {
                WHERE("create_time >= #{startTime}");
            }
            
            if (condition.containsKey("endTime") && condition.get("endTime") != null) {
                WHERE("create_time <= #{endTime}");
            }
            
            if (condition.containsKey("orderBy") && condition.get("orderBy") != null) {
                ORDER_BY(condition.get("orderBy") + " " + condition.get("orderDir"));
            } else {
                ORDER_BY("user_id ASC");
            }
        }}.toString();
    }
    
    public String insertUser(User user) {
        return new SQL() {{
            INSERT_INTO("users");
            
            if (user.getUsername() != null) {
                VALUES("user_name", "#{username}");
            }
            
            if (user.getEmail() != null) {
                VALUES("user_email", "#{email}");
            }
            
            if (user.getCreateTime() != null) {
                VALUES("create_time", "#{createTime}");
            }
        }}.toString();
    }
    
    public String updateUser(User user) {
        return new SQL() {{
            UPDATE("users");
            
            if (user.getUsername() != null) {
                SET("user_name = #{username}");
            }
            
            if (user.getEmail() != null) {
                SET("user_email = #{email}");
            }
            
            WHERE("user_id = #{id}");
        }}.toString();
    }
}
📝 2.2 结果映射注解
@Results(id = "userResultMap", value = {
    @Result(property = "id", column = "user_id", id = true),
    @Result(property = "username", column = "user_name"),
    @Result(property = "email", column = "user_email"),
    @Result(property = "createTime", column = "create_time"),
    // 一对一关联映射
    @Result(property = "department", column = "dept_id", 
            one = @One(select = "com.example.mapper.DepartmentMapper.selectDepartmentById")),
    // 一对多关联映射
    @Result(property = "roles", column = "user_id", 
            many = @Many(select = "com.example.mapper.RoleMapper.selectRolesByUserId"))
})
@Select("SELECT * FROM users WHERE user_id = #{id}")
User selectUserWithDetails(Long id);
📝 2.3 缓存注解
@CacheNamespace(implementation = RedisCache.class, eviction = LruCache.class, flushInterval = 60000)
public interface UserMapper {
    
    @Select("SELECT * FROM users WHERE user_id = #{id}")
    @Options(useCache = true, flushCache = Options.FlushCachePolicy.DEFAULT)
    User selectUserById(Long id);
    
    @Insert("INSERT INTO users (user_name, user_email) VALUES (#{username}, #{email})")
    @Options(useCache = false, flushCache = Options.FlushCachePolicy.TRUE)
    int insertUser(User user);
}
📝 2.4 批量操作注解
@Insert("<script>" +
        "INSERT INTO users (user_name, user_email, create_time) VALUES " +
        "<foreach collection='list' item='item' separator=','>" +
        "(#{item.username}, #{item.email}, #{item.createTime})" +
        "</foreach>" +
        "</script>")
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "user_id")
int batchInsertUsers(@Param("list") List<User> users);

🎉 3. 注解配置的最佳实践

📝 3.1 合理选择配置方式
  • 简单查询:优先使用注解配置
  • 复杂查询:优先使用XML配置
  • 动态SQL:根据复杂度选择注解或XML
📝 3.2 参数处理技巧
// 单个参数
@Select("SELECT * FROM users WHERE user_id = #{id}")
User selectUserById(Long id);

// 多个参数(使用@Param注解)
@Select("SELECT * FROM users WHERE user_name = #{username} AND user_email = #{email}")
User selectUserByUsernameAndEmail(@Param("username") String username, @Param("email") String email);

// Map参数
@Select("SELECT * FROM users WHERE user_name LIKE CONCAT('%', #{username}, '%')")
List<User> selectUsersByMap(Map<String, Object> params);

// JavaBean参数
@Select("SELECT * FROM users WHERE user_name = #{username}")
List<User> selectUsersByUser(User user);
📝 3.3 类型转换配置
@Select("SELECT * FROM users WHERE create_time BETWEEN #{startTime} AND #{endTime}")
List<User> selectUsersByTimeRange(
    @Param("startTime") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
    @Param("endTime") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime
);

// 自定义类型处理器
@Select("SELECT * FROM users WHERE user_id = #{id}")
@Result(property = "password", column = "password", typeHandler = EncryptTypeHandler.class)
User selectUserWithEncryptedPassword(Long id);
📝 3.4 事务管理
@Service
public class UserServiceImpl implements UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    @Autowired
    private RoleMapper roleMapper;
    
    @Transactional(rollbackFor = Exception.class)
    public void createUserWithRoles(User user, List<Role> roles) {
        userMapper.insertUser(user);
        for (Role role : roles) {
            role.setUserId(user.getId());
            roleMapper.insertRole(role);
        }
    }
}
📝 3.5 性能优化
@Select("SELECT * FROM users")
@Options(fetchSize = 1000, timeout = 30)
List<User> selectAllUsers();

@Update("UPDATE users SET user_name = #{username} WHERE user_id = #{id}")
@Options(useCache = false, flushCache = Options.FlushCachePolicy.TRUE)
int updateUser(User user);

🎉 4. 注解配置的优缺点

优点

  • 配置简洁,无需额外的XML文件
  • 编译期可以检查接口方法的正确性
  • 适合简单查询和快速开发场景
  • 与Spring Boot等框架集成更紧密

缺点

  • 复杂SQL和动态SQL的可读性较差
  • 不便于DBA参与SQL优化
  • 大型项目中可能导致接口过于臃肿

🍊 MyBatis核心知识点 之 Mapper接口绑定:动态SQL

🎉 1. 动态SQL基础

动态SQL是MyBatis最强大的特性之一,它允许你根据不同的条件动态生成SQL语句。MyBatis提供了以下动态SQL标签:

  • <if>:条件判断
  • <choose>/<when>/<otherwise>:多条件分支
  • <trim>/<where>/<set>:SQL片段处理
  • <foreach>:循环遍历
  • <bind>:变量绑定

🎉 2. 动态SQL标签详解

📝 2.1 <if>标签

<if>标签用于条件判断,当条件满足时才会包含对应的SQL片段:

<select id="selectUsersByCondition" parameterType="com.example.dto.UserQuery" resultMap="userResultMap">
    SELECT * FROM users
    WHERE 1=1
    <if test="username != null and username != ''">
        AND user_name LIKE CONCAT('%', #{username}, '%')
    </if>
    <if test="email != null and email != ''">
        AND user_email = #{email}
    </if>
    <if test="startTime != null">
        AND create_time >= #{startTime}
    </if>
    <if test="endTime != null">
        AND create_time <= #{endTime}
    </if>
</select>
📝 2.2 <choose>/<when>/<otherwise>标签

这组标签用于多条件分支判断,类似于Java中的switch-case语句:

<select id="selectUsersByCondition" parameterType="com.example.dto.UserQuery" resultMap="userResultMap">
    SELECT * FROM users
    <where>
        <choose>
            <when test="id != null">
                user_id = #{id}
            </when>
            <when test="username != null and username != ''">
                user_name LIKE CONCAT('%', #{username}, '%')
            </when>
            <when test="email != null and email != ''">
                user_email = #{email}
            </when>
            <otherwise>
                1=1
            </otherwise>
        </choose>
    </where>
</select>
📝 2.3 <trim>/<where>/<set>标签

这些标签用于处理SQL片段的前缀和后缀:

<where>标签示例

<select id="selectUsersByCondition" parameterType="com.example.dto.UserQuery" resultMap="userResultMap">
    SELECT * FROM users
    <where>
        <if test="username != null and username != ''">
            user_name LIKE CONCAT('%', #{username}, '%')
        </if>
        <if test="email != null and email != ''">
            AND user_email = #{email}
        </if>
        <if test="startTime != null">
            AND create_time >= #{startTime}
        </if>
    </where>
</select>

<set>标签示例

<update id="updateUser" parameterType="com.example.entity.User">
    UPDATE users
    <set>
        <if test="username != null and username != ''">
            user_name = #{username},
        </if>
        <if test="email != null and email != ''">
            user_email = #{email},
        </if>
        <if test="createTime != null">
            create_time = #{createTime},
        </if>
    </set>
    WHERE user_id = #{id}
</update>

<trim>标签示例

<insert id="insertUser" parameterType="com.example.entity.User">
    INSERT INTO users
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="username != null">user_name,</if>
        <if test="email != null">user_email,</if>
        <if test="createTime != null">create_time,</if>
    </trim>
    VALUES
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="username != null">#{username},</if>
        <if test="email != null">#{email},</if>
        <if test="createTime != null">#{createTime},</if>
    </trim>
</insert>
📝 2.4 <foreach>标签

<foreach>标签用于循环遍历集合或数组:

批量插入示例

<insert id="batchInsertUsers" parameterType="java.util.List">
    INSERT INTO users (user_name, user_email, create_time)
    VALUES
    <foreach collection="list" item="item" separator=",">
        (#{item.username}, #{item.email}, #{item.createTime})
    </foreach>
</insert>

批量删除示例

<delete id="batchDeleteUsers" parameterType="java.util.List">
    DELETE FROM users
    WHERE user_id IN
    <foreach collection="list" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</delete>

动态IN查询示例

<select id="selectUsersByIds" parameterType="java.util.List" resultMap="userResultMap">
    SELECT * FROM users
    WHERE user_id IN
    <foreach collection="list" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</select>
📝 2.5 <bind>标签

<bind>标签用于在OGNL表达式中创建一个变量:

<select id="selectUsersByUsername" parameterType="java.lang.String" resultMap="userResultMap">
    <bind name="pattern" value="'%' + username + '%'"/>
    SELECT * FROM users
    WHERE user_name LIKE #{pattern}
</select>

🎉 3. 动态SQL高级技巧

📝 3.1 动态SQL与注解结合
@Select("<script>" +
        "SELECT * FROM users " +
        "<where>" +
        "<if test='username != null and username != \"\"'>" +
        "AND user_name LIKE CONCAT('%', #{username}, '%') " +
        "</if>" +
        "<if test='email != null and email != \"\"'>" +
        "AND user_email = #{email} " +
        "</if>" +
        "</where>" +
        "</script>")
List<User> selectUsersByCondition(UserQuery query);
📝 3.2 动态SQL与SQL片段结合
<!-- 定义SQL片段 -->
<sql id="userColumns">
    user_id, user_name, user_email, create_time
</sql>

<!-- 使用SQL片段 -->
<select id="selectUserById" parameterType="java.lang.Long" resultMap="userResultMap">
    SELECT <include refid="userColumns"/> FROM users WHERE user_id = #{id}
</select>

<!-- 动态SQL片段 -->
<sql id="userWhereCondition">
    <where>
        <if test="username != null and username != ''">
            user_name LIKE CONCAT('%', #{username}, '%')
        </if>
        <if test="email != null and email != ''">
            AND user_email = #{email}
        </if>
    </where>
</sql>

<select id="selectUsersByCondition" parameterType="com.example.dto.UserQuery" resultMap="userResultMap">
    SELECT <include refid="userColumns"/> FROM users
    <include refid="userWhereCondition"/>
</select>
📝 3.3 动态SQL与类型处理器结合
<select id="selectUsersByEncryptedEmail" parameterType="java.lang.String" resultMap="userResultMap">
    SELECT * FROM users
    WHERE user_email = #{email, typeHandler=com.example.typehandler.EncryptTypeHandler}
</select>
📝 3.4 动态SQL与缓存结合
<select id="selectUsersByCondition" parameterType="com.example.dto.UserQuery" resultMap="userResultMap" useCache="true">
    SELECT * FROM users
    <where>
        <if test="username != null and username != ''">
            user_name LIKE CONCAT('%', #{username}, '%')
        </if>
        <if test="email != null and email != ''">
            AND user_email = #{email}
        </if>
    </where>
</select>

🎉 4. 动态SQL性能优化

📝 4.1 避免过度动态化

虽然动态SQL非常灵活,但过度使用会导致SQL语句过于复杂,影响性能和可读性。应尽量保持SQL的简洁性。

📝 4.2 使用预编译语句

MyBatis默认使用预编译语句,这有助于提高性能和安全性。避免在动态SQL中使用字符串拼接,而是使用参数绑定。

📝 4.3 合理使用缓存

对于不常变化的动态SQL查询,可以开启缓存来提高性能:

<select id="selectUsersByCondition" parameterType="com.example.dto.UserQuery" resultMap="userResultMap" useCache="true">
    <!-- 动态SQL内容 -->
</select>
📝 4.4 优化动态SQL结构

将复杂的动态SQL拆分为多个简单的动态SQL,或者使用SQL片段来提高可读性和维护性:

<!-- 拆分复杂动态SQL -->
<sql id="baseCondition">
    <if test="username != null and username != ''">
        user_name LIKE CONCAT('%', #{username}, '%')
    </if>
</sql>

<sql id="timeCondition">
    <if test="startTime != null">
        AND create_time >= #{startTime}
    </if>
    <if test="endTime != null">
        AND create_time <= #{endTime}
    </if>
</sql>

<select id="selectUsersByCondition" parameterType="com.example.dto.UserQuery" resultMap="userResultMap">
    SELECT * FROM users
    <where>
        <include refid="baseCondition"/>
        <include refid="timeCondition"/>
    </where>
</select>

🍊 MyBatis核心知识点 之 Mapper接口绑定:参数绑定

🎉 1. 参数绑定基础

MyBatis的参数绑定是将Java方法参数映射到SQL语句中的过程。MyBatis提供了多种参数绑定方式:

  • 单个参数绑定
  • 多个参数绑定
  • Map参数绑定
  • JavaBean参数绑定
  • 集合/数组参数绑定

🎉 2. 参数绑定方式详解

📝 2.1 单个参数绑定

当方法只有一个参数时,MyBatis可以直接使用参数名或任意名称进行绑定:

// Mapper接口方法
User selectUserById(Long id);

// XML配置
<select id="selectUserById" parameterType="java.lang.Long" resultMap="userResultMap">
    SELECT * FROM users WHERE user_id = #{id}
</select>

或者使用任意名称:

<select id="selectUserById" parameterType="java.lang.Long" resultMap="userResultMap">
    SELECT * FROM users WHERE user_id = #{userId}
</select>
📝 2.2 多个参数绑定

当方法有多个参数时,需要使用@Param注解来指定参数名称:

// Mapper接口方法
User selectUserByUsernameAndEmail(@Param("username") String username, @Param("email") String email);

// XML配置
<select id="selectUserByUsernameAndEmail" resultMap="userResultMap">
    SELECT * FROM users 
    WHERE user_name = #{username} 
    AND user_email = #{email}
</select>

如果不使用@Param注解,MyBatis会使用参数索引进行绑定:

// Mapper接口方法
User selectUserByUsernameAndEmail(String username, String email);

// XML配置
<select id="selectUserByUsernameAndEmail" resultMap="userResultMap">
    SELECT * FROM users 
    WHERE user_name = #{0} 
    AND user_email = #{1}
</select>

或者使用param1param2等名称:

<select id="selectUserByUsernameAndEmail" resultMap="userResultMap">
    SELECT * FROM users 
    WHERE user_name = #{param1} 
    AND user_email = #{param2}
</select>
📝 2.3 Map参数绑定

当方法参数是Map时,可以直接使用Map中的key进行绑定:

// Mapper接口方法
List<User> selectUsersByMap(Map<String, Object> params);

// XML配置
<select id="selectUsersByMap" parameterType="java.util.Map" resultMap="userResultMap">
    SELECT * FROM users
    <where>
        <if test="username != null">
            user_name LIKE CONCAT('%', #{username}, '%')
        </if>
        <if test="email != null">
            AND user_email = #{email}
        </if>
    </where>
</select>

使用示例:

Map<String, Object> params = new HashMap<>();
params.put("username", "admin");
params.put("email", "admin@example.com");
List<User> users = userMapper.selectUsersByMap(params);
📝 2.4 JavaBean参数绑定

当方法参数是JavaBean时,可以直接使用JavaBean的属性名进行绑定:

// JavaBean
public class UserQuery {
    private String username;
    private String email;
    private LocalDateTime startTime;
    private LocalDateTime endTime;
    
    // getter和setter方法
}

// Mapper接口方法
List<User> selectUsersByQuery(UserQuery query);

// XML配置
<select id="selectUsersByQuery" parameterType="com.example.dto.UserQuery" resultMap="userResultMap">
    SELECT * FROM users
    <where>
        <if test="username != null and username != ''">
            user_name LIKE CONCAT('%', #{username}, '%')
        </if>
        <if test="email != null and email != ''">
            AND user_email = #{email}
        </if>
        <if test="startTime != null">
            AND create_time >= #{startTime}
        </if>
        <if test="endTime != null">
            AND create_time <= #{endTime}
        </if>
    </where>
</select>
📝 2.5 集合/数组参数绑定

当方法参数是集合或数组时,可以使用<foreach>标签进行绑定:

// Mapper接口方法
List<User> selectUsersByIds(List<Long> ids);

// XML配置
<select id="selectUsersByIds" parameterType="java.util.List" resultMap="userResultMap">
    SELECT * FROM users
    WHERE user_id IN
    <foreach collection="list" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</select>

对于数组参数:

// Mapper接口方法
List<User> selectUsersByIds(Long[] ids);

// XML配置
<select id="selectUsersByIds" parameterType="java.lang.Long[]" resultMap="userResultMap">
    SELECT * FROM users
    WHERE user_id IN
    <foreach collection="array" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</select>
📝 2.6 混合参数绑定

当方法参数是多种类型的混合时,可以使用@Param注解来区分:

// Mapper接口方法
List<User> selectUsersByMix(@Param("query") UserQuery query, @Param("ids") List<Long> ids);

// XML配置
<select id="selectUsersByMix" resultMap="userResultMap">
    SELECT * FROM users
    <where>
        <if test="query.username != null and query.username != ''">
            user_name LIKE CONCAT('%', #{query.username}, '%')
        </if>
        <if test="ids != null and ids.size() > 0">
            AND user_id IN
            <foreach collection="ids" item="id" open="(" close=")" separator=",">
                #{id}
            </foreach>
        </if>
    </where>
</select>

🎉 3. 参数绑定高级技巧

📝 3.1 参数类型指定

可以在参数绑定中指定参数的JDBC类型:

<insert id="insertUser" parameterType="com.example.entity.User">
    INSERT INTO users (user_name, user_email, create_time)
    VALUES (#{username, jdbcType=VARCHAR}, #{email, jdbcType=VARCHAR}, #{createTime, jdbcType=TIMESTAMP})
</insert>
📝 3.2 参数模式指定

可以指定参数的模式(IN、OUT、INOUT):

<select id="selectUserById" parameterType="java.lang.Long" resultMap="userResultMap">
    SELECT * FROM users WHERE user_id = #{id, mode=IN}
</select>
📝 3.3 参数类型处理器指定

可以指定参数的类型处理器:

<insert id="insertUser" parameterType="com.example.entity.User">
    INSERT INTO users (user_name, user_email, password)
    VALUES (#{username}, #{email}, #{password, typeHandler=com.example.typehandler.EncryptTypeHandler})
</insert>
📝 3.4 参数默认值指定

可以为参数指定默认值:

<select id="selectUsersByUsername" parameterType="java.lang.String" resultMap="userResultMap">
    SELECT * FROM users
    WHERE user_name LIKE #{username, javaType=java.lang.String, jdbcType=VARCHAR, defaultValue='%'}
</select>
📝 3.5 复杂参数绑定

对于复杂的参数类型,可以使用OGNL表达式进行绑定:

// JavaBean
public class User {
    private Long id;
    private String username;
    private String email;
    private Department department;
    
    // getter和setter方法
}

public class Department {  
    private Long id;
    private String name;
    
    // getter和setter方法
}

// Mapper接口方法
int insertUser(User user);

// XML配置
<insert id="insertUser" parameterType="com.example.entity.User">
    INSERT INTO users (user_id, user_name, user_email, dept_id)
    VALUES (#{id}, #{username}, #{email}, #{department.id})
</insert>

🎉 4. 参数绑定常见问题

📝 4.1 参数名称不匹配

当使用@Param注解时,参数名称必须与XML中的参数名称一致:

// 正确示例
User selectUserByUsernameAndEmail(@Param("username") String username, @Param("email") String email);

// 错误示例
User selectUserByUsernameAndEmail(@Param("name") String username, @Param("mail") String email);
📝 4.2 集合参数绑定错误

当绑定集合参数时,collection属性的值必须正确:

  • List参数:collection="list"
  • Set参数:collection="collection"
  • 数组参数:collection="array"
  • 自定义名称:使用@Param注解指定
// 正确示例
List<User> selectUsersByIds(@Param("ids") List<Long> ids);

// XML配置
<foreach collection="ids" item="id" open="(" close=")" separator=",">
    #{id}
</foreach>
📝 4.3 类型不匹配

当参数类型与数据库字段类型不匹配时,需要指定类型处理器:

<insert id="insertUser" parameterType="com.example.entity.User">
    INSERT INTO users (user_name, create_time)
    VALUES (#{username}, #{createTime, typeHandler=com.example.typehandler.LocalDateTimeTypeHandler})
</insert>
📝 4.4 NULL值处理

当参数可能为NULL时,需要指定JDBC类型:

<insert id="insertUser" parameterType="com.example.entity.User">
    INSERT INTO users (user_name, user_email)
    VALUES (#{username, jdbcType=VARCHAR}, #{email, jdbcType=VARCHAR})
</insert>

🍊 MyBatis核心知识点 之 Mapper接口绑定:性能优化

🎉 1. 批量操作优化

📝 1.1 批量插入优化

传统批量插入

// 低效的批量插入
public void batchInsertUsers(List<User> users) {
    for (User user : users) {
        userMapper.insertUser(user);
    }
}

优化后的批量插入

<!-- XML配置 -->
<insert id="batchInsertUsers" parameterType="java.util.List">
    INSERT INTO users (user_name, user_email, create_time)
    VALUES
    <foreach collection="list" item="item" separator=",">
        (#{item.username}, #{item.email}, #{item.createTime})
    </foreach>
</insert>

// Mapper接口方法
int batchInsertUsers(List<User> users);

// 调用方式
userMapper.batchInsertUsers(users);

JDBC批量插入

// 使用JDBC批量插入
public void batchInsertUsers(List<User> users) {
    SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
    try {
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        for (int i = 0; i < users.size(); i++) {
            userMapper.insertUser(users.get(i));
            if (i % 1000 == 0) {
                sqlSession.flushStatements();
            }
        }
        sqlSession.flushStatements();
        sqlSession.commit();
    } finally {
        sqlSession.close();
    }
}
📝 1.2 批量更新优化

传统批量更新

// 低效的批量更新
public void batchUpdateUsers(List<User> users) {
    for (User user : users) {
        userMapper.updateUser(user);
    }
}

优化后的批量更新

<!-- XML配置 -->
<update id="batchUpdateUsers" parameterType="java.util.List">
    <foreach collection="list" item="item" separator=";">
        UPDATE users
        SET user_name = #{item.username},
            user_email = #{item.email}
        WHERE user_id = #{item.id}
    </foreach>
</update>

// 注意:需要开启allowMultiQueries=true
jdbc:mysql://localhost:3306/test?allowMultiQueries=true

CASE WHEN批量更新

<update id="batchUpdateUsers" parameterType="java.util.List">
    UPDATE users
    SET 
        user_name = CASE user_id
        <foreach collection="list" item="item">
            WHEN #{item.id} THEN #{item.username}
        </foreach>
        END,
        user_email = CASE user_id
        <foreach collection="list" item="item">
            WHEN #{item.id} THEN #{item.email}
        </foreach>
        END
    WHERE user_id IN
    <foreach collection="list" item="item" open="(" close=")" separator=",">
        #{item.id}
    </foreach>
</update>
📝 1.3 批量删除优化
<!-- XML配置 -->
<delete id="batchDeleteUsers" parameterType="java.util.List">
    DELETE FROM users
    WHERE user_id IN
    <foreach collection="list" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</delete>

🎉 2. 缓存优化

📝 2.1 一级缓存优化

MyBatis的一级缓存是SqlSession级别的缓存,默认开启。可以通过以下方式优化:

// 关闭一级缓存
sqlSession.clearCache();

// 或者在查询时关闭缓存
@Options(useCache = false)
User selectUserById(Long id);
📝 2.2 二级缓存优化

MyBatis的二级缓存是Mapper级别的缓存,需要手动开启:

<!-- 开启二级缓存 -->
<cache 
    eviction="LRU"
    flushInterval="60000"
    size="512"
    readOnly="true"/>

<!-- 配置缓存引用 -->
<cache-ref namespace="com.example.mapper.UserMapper"/>

<!-- 配置不使用缓存的语句 -->
<select id="selectUserById" parameterType="java.lang.Long" resultMap="userResultMap" useCache="false">
    SELECT * FROM users WHERE user_id = #{id}
</select>
📝 2.3 自定义缓存

可以实现自定义缓存,如Redis缓存:

public class RedisCache implements Cache {
    private final String id;
    private final RedisTemplate<String, Object> redisTemplate;
    private final long timeout = 60000;
    
    public RedisCache(String id) {
        this.id = id;
        this.redisTemplate = ApplicationContextHolder.getBean("redisTemplate");
    }
    
    @Override
    public String getId() {
        return id;
    }
    
    @Override
    public void putObject(Object key, Object value) {
        redisTemplate.opsForValue().set(key.toString(), value, timeout, TimeUnit.MILLISECONDS);
    }
    
    @Override
    public Object getObject(Object key) {
        return redisTemplate.opsForValue().get(key.toString());
    }
    
    @Override
    public Object removeObject(Object key) {
        return redisTemplate.delete(key.toString());
    }
    
    @Override
    public void clear() {
        redisTemplate.delete(redisTemplate.keys("*" + id + "*"));
    }
    
    @Override
    public int getSize() {
        return redisTemplate.keys("*" + id + "*").size();
    }
}

使用自定义缓存:

@CacheNamespace(implementation = RedisCache.class)
public interface UserMapper {
    // Mapper方法
}

🎉 3. 其他性能优化技巧

📝 3.1 延迟加载优化
<!-- 在全局配置中开启延迟加载 -->
<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

<!-- 在ResultMap中配置延迟加载 -->
<resultMap id="userResultMap" type="com.example.entity.User">
    <id property="id" column="user_id"/>
    
    <!-- 一对一延迟加载 -->
    <association property="department" column="dept_id" 
                 select="com.example.mapper.DepartmentMapper.selectDepartmentById" 
                 fetchType="lazy"/>
    
    <!-- 一对多延迟加载 -->
    <collection property="roles" column="user_id" 
                select="com.example.mapper.RoleMapper.selectRolesByUserId" 
                fetchType="lazy"/>
</resultMap>
📝 3.2 结果集映射优化
<!-- 使用构造函数映射 -->
<resultMap id="userResultMap" type="com.example.entity.User">
    <constructor>
        <idArg column="user_id" javaType="java.lang.Long"/>
        <arg column="user_name" javaType="java.lang.String"/>
        <arg column="user_email" javaType="java.lang.String"/>
    </constructor>
</resultMap>

<!-- 使用自动映射 -->
<resultMap id="userResultMap" type="com.example.entity.User" autoMapping="true">
    <id property="id" column="user_id"/>
</resultMap>
📝 3.3 SQL优化
<!-- 使用fetchSize优化大数据量查询 -->
<select id="selectAllUsers" resultMap="userResultMap" fetchSize="1000">
    SELECT * FROM users
</select>

<!-- 使用timeout优化查询超时 -->
<select id="selectUserById" parameterType="java.lang.Long" resultMap="userResultMap" timeout="30">
    SELECT * FROM users WHERE user_id = #{id}
</select>

<!-- 使用resultSetType优化结果集类型 -->
<select id="selectAllUsers" resultMap="userResultMap" resultSetType="SCROLL_SENSITIVE">
    SELECT * FROM users
</select>
📝 3.4 连接池优化
<!-- 配置连接池 -->
<dataSource type="POOLED">
    <property name="driver" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    <!-- 连接池配置 -->
    <property name="poolMaximumActiveConnections" value="50"/>
    <property name="poolMaximumIdleConnections" value="20"/>
    <property name="poolMaximumCheckoutTime" value="20000"/>
    <property name="poolTimeToWait" value="20000"/>
    <property name="poolPingQuery" value="SELECT 1"/>
    <property name="poolPingEnabled" value="true"/>
    <property name="poolPingConnectionsNotUsedFor" value="3600000"/>
</dataSource>
📝 3.5 批量操作优化
// 使用批量执行器
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    for (User user : users) {
        userMapper.insertUser(user);
    }
    sqlSession.commit();
} finally {
    sqlSession.close();
}

🍊 总结

MyBatis的Mapper接口绑定是MyBatis框架的核心特性之一,它提供了灵活的配置方式和强大的功能。通过本文的介绍,我们了解了:

  • XML配置方式的基础和高级用法
  • 注解配置方式的基础和高级用法
  • 动态SQL的各种标签和用法
  • 参数绑定的各种方式和技巧
  • 性能优化的各种方法和实践

在实际项目中,应根据具体情况选择合适的配置方式和优化策略,以达到最佳的性能和可维护性。MyBatis的灵活性和强大功能使其成为Java持久层框架的优秀选择,掌握MyBatis的核心知识点对于Java开发者来说是非常重要的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值