1. Mybatis XML方式的数据操作基础
Mybatis作为Java生态中最受欢迎的ORM框架之一,其XML配置方式一直是核心特性。在实际项目中,新增(insert)、删除(delete)和修改(update)这三大基础操作占据了日常开发的绝大部分场景。与注解方式相比,XML配置提供了更清晰的SQL可视化管理和更灵活的动态SQL支持。
提示:虽然MyBatis 3.x开始支持注解方式,但复杂业务场景下XML仍然是首选方案,特别是在需要动态SQL时。
1.1 环境准备与基础配置
在开始编写XML映射文件前,需要确保项目已正确集成MyBatis。以Maven项目为例,基础依赖如下:
xml复制<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
MyBatis的核心配置文件mybatis-config.xml中需要指定XML映射文件的位置:
xml复制<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
1.2 XML映射文件结构
一个完整的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">
<!-- 这里定义SQL语句 -->
</mapper>
namespace属性对应Mapper接口的全限定名,这是MyBatis将XML与接口绑定的关键。
2. 新增(insert)操作详解
2.1 基础insert语句
最简单的insert操作示例:
xml复制<insert id="insertUser" parameterType="com.example.model.User">
INSERT INTO user (id, name, email)
VALUES (#{id}, #{name}, #{email})
</insert>
id属性对应Mapper接口中的方法名parameterType指定参数类型(可省略,MyBatis通常能自动推断)#{}是MyBatis的参数占位符,会进行预编译防止SQL注入
2.2 主键回填策略
插入数据后获取自增主键是常见需求,MyBatis提供了多种方式:
xml复制<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (name, email) VALUES (#{name}, #{email})
</insert>
useGeneratedKeys="true"启用自动生成键功能keyProperty指定将生成的主键值设置到参数的哪个属性
对于不支持自增主键的数据库(如Oracle),可以使用<selectKey>:
xml复制<insert id="insertUser">
<selectKey keyProperty="id" resultType="long" order="BEFORE">
SELECT SEQ_USER.NEXTVAL FROM DUAL
</selectKey>
INSERT INTO user (id, name, email)
VALUES (#{id}, #{name}, #{email})
</insert>
2.3 批量插入优化
MyBatis 3.3.1+支持批量插入语法:
xml复制<insert id="batchInsert" parameterType="java.util.List">
INSERT INTO user (name, email) VALUES
<foreach collection="list" item="item" separator=",">
(#{item.name}, #{item.email})
</foreach>
</insert>
注意:MySQL默认接受的SQL语句长度有限制(max_allowed_packet),大批量插入时需要考虑分批次。
3. 删除(delete)操作实践
3.1 基础delete语句
xml复制<delete id="deleteById">
DELETE FROM user WHERE id = #{id}
</delete>
3.2 批量删除实现
使用foreach实现批量删除:
xml复制<delete id="batchDelete">
DELETE FROM user WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
3.3 级联删除考虑
当存在外键约束时,直接删除可能违反约束。有几种处理方案:
- 先删除从表数据再删除主表数据
- 使用数据库级联删除(ON DELETE CASCADE)
- 使用MyBatis的事务管理确保操作原子性
java复制@Transactional
public void deleteUserWithRelatedData(Long userId) {
// 先删除关联数据
commentMapper.deleteByUserId(userId);
orderMapper.deleteByUserId(userId);
// 最后删除用户
userMapper.deleteById(userId);
}
4. 修改(update)操作进阶
4.1 基础update语句
xml复制<update id="updateUser" parameterType="com.example.model.User">
UPDATE user
SET name = #{name}, email = #{email}
WHERE id = #{id}
</update>
4.2 动态字段更新
实际业务中经常需要只更新部分字段,MyBatis提供了强大的动态SQL支持:
xml复制<update id="updateSelective" parameterType="com.example.model.User">
UPDATE user
<set>
<if test="name != null">name = #{name},</if>
<if test="email != null">email = #{email},</if>
</set>
WHERE id = #{id}
</update>
<set>标签会自动处理结尾多余的逗号,避免SQL语法错误。
4.3 乐观锁实现
在高并发场景下,乐观锁是常见解决方案。实现方式通常是在表中增加version字段:
xml复制<update id="updateWithVersion">
UPDATE user
SET name = #{name}, version = version + 1
WHERE id = #{id} AND version = #{version}
</update>
业务代码中需要检查返回值(受影响行数),如果为0表示更新失败,需要处理并发冲突。
5. 安全注意事项与最佳实践
5.1 SQL注入防护
虽然MyBatis的#{}语法默认使用预编译防止SQL注入,但使用${}时仍需谨慎:
xml复制<!-- 不安全的写法 -->
<select id="findByOrder" parameterType="string">
SELECT * FROM user ORDER BY ${orderBy}
</select>
<!-- 安全的替代方案 -->
<select id="findByOrder">
SELECT * FROM user
ORDER BY
<choose>
<when test="orderBy == 'name'">name</when>
<when test="orderBy == 'email'">email</when>
<otherwise>id</otherwise>
</choose>
</select>
重要:奇安信等安全扫描工具会检测到使用
${}可能导致的SQL注入漏洞,应尽量避免在用户输入可控的场景下使用。
5.2 事务管理
MyBatis本身不提供事务管理,需要与Spring等框架集成:
java复制@Service
public class UserService {
private final UserMapper userMapper;
@Transactional
public void updateUserInfo(User user) {
// 多个操作将在一个事务中执行
userMapper.updateSelective(user);
logMapper.insertLog(user.getId(), "UPDATE");
}
}
5.3 性能优化建议
- 批量操作:使用
foreach实现批量操作比循环单次操作效率高得多 - 二级缓存:合理配置缓存可以显著提升查询性能
- 延迟加载:对于关联对象,使用
lazyLoadingEnabled=true避免不必要的数据加载 - SQL优化:复杂的SQL语句应考虑添加适当的索引
6. 常见问题排查
6.1 XML文件未生效
可能原因及解决方案:
- 文件位置错误:确保XML文件在
resources目录下,且路径与mybatis-config.xml中的配置一致 - 命名空间不匹配:XML中的
namespace必须与Mapper接口全限定名完全相同 - 未重新编译:修改XML后需要重新构建项目
6.2 参数绑定异常
典型错误信息:
code复制org.apache.ibatis.binding.BindingException: Parameter 'xxx' not found
解决方案:
- 检查
#{}中的参数名是否与方法参数名一致 - 使用
@Param注解明确指定参数名:
java复制List<User> findByNameAndEmail(@Param("name") String name, @Param("email") String email);
6.3 动态SQL语法错误
常见错误包括:
<if>条件判断中使用错误的运算符(应使用==而不是=)- 忘记闭合标签
- 在
<set>或<trim>外使用逗号
调试技巧:
- 开启MyBatis日志查看实际执行的SQL
- 使用MyBatis提供的
@org.apache.ibatis.annotations.Param注解调试参数
7. 高级技巧与扩展
7.1 类型处理器(TypeHandler)
自定义类型处理器可以处理特殊数据类型:
java复制public class StringArrayTypeHandler extends BaseTypeHandler<String[]> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
String[] parameter, JdbcType jdbcType) {
ps.setString(i, String.join(",", parameter));
}
// 其他方法实现...
}
在XML中指定:
xml复制<resultMap id="userResultMap" type="User">
<result column="tags" property="tags"
typeHandler="com.example.handler.StringArrayTypeHandler"/>
</resultMap>
7.2 存储过程调用
MyBatis支持调用存储过程:
xml复制<select id="callProcedure" statementType="CALLABLE">
{call update_user_status(
#{id, mode=IN},
#{newStatus, mode=IN},
#{result, mode=OUT, jdbcType=INTEGER}
)}
</select>
7.3 多数据库支持
通过databaseId属性实现多数据库支持:
xml复制<insert id="insertUser" databaseId="mysql">
<!-- MySQL特有语法 -->
</insert>
<insert id="insertUser" databaseId="oracle">
<!-- Oracle特有语法 -->
</insert>
在mybatis-config.xml中配置数据库厂商标识:
xml复制<databaseIdProvider type="VENDOR">
<property name="MySQL" value="mysql"/>
<property name="Oracle" value="oracle"/>
</databaseIdProvider>
在实际开发中,我发现合理组织XML文件结构能显著提升维护效率。通常我会按功能模块拆分XML文件,而不是把所有SQL都放在一个文件中。对于复杂的动态SQL,适当添加注释说明业务逻辑也非常重要。
