🌟 MyBatis核心知识点 之 Mapper接口绑定:配置方式
🍊 引言:从传统XML到现代注解的演进
在金融交易系统开发中,某电商平台的技术团队曾因多环境SQL版本管理导致日均30+次生产环境异常。工程师张磊在排查发现,其团队采用的早期XML映射方案存在三大致命缺陷:
- SQL与Java代码物理分离导致版本不同步:某次灰度发布因SQL文件未同步引发交易冻结
- 动态SQL需重复编写多个MapStatement:单张订单支付接口产生17个重复映射
- 复杂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>
或者使用param1、param2等名称:
<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开发者来说是非常重要的。
1301

被折叠的 条评论
为什么被折叠?



