MyBatis持久层实现
package com.example.usermanagement.mapper;
import com.example.usermanagement.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 用户Mapper接口
* @Mapper: 标识这是MyBatis的Mapper接口
*/
@Mapper
public interface UserMapper {
// 插入用户
int insert(User user);
// 根据ID删除用户
int deleteById(Long id);
// 更新用户信息
int update(User user);
// 根据ID查询用户
User selectById(Long id);
// 根据用户名查询用户
User selectByUsername(String username);
// 查询所有用户
List<User> selectAll();
// 分页查询用户
List<User> selectByPage(@Param("offset") Integer offset,
@Param("limit") Integer limit);
// 统计用户总数
int count();
/**
* 根据邮箱查询用户
* @param email 邮箱地址
* @return 用户信息
*/
User selectByEmail(String email);
List<User> searchByUsername(@Param("username") String username,
@Param("offset") int offset,
@Param("limit") int limit);
int countByUsername(@Param("username") String username);
}
<?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.usermanagement.mapper.UserMapper">
<!-- 结果映射:定义数据库字段与实体类属性的映射关系 -->
<resultMap id="BaseResultMap" type="com.example.usermanagement.entity.User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="email" property="email"/>
<result column="phone" property="phone"/>
<result column="status" property="status"/>
<result column="score" property="score"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<!-- 基础字段列表 -->
<sql id="Base_Column_List">
id, username, password, email, phone, status, score, create_time, update_time
</sql>
<!-- 插入用户 -->
<!-- 原来的写法 -->
<insert id="insert" parameterType="com.example.usermanagement.entity.User"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (username, password, email, phone, status, score)
VALUES (#{username}, #{password}, #{email}, #{phone}, #{status}, #{score})
</insert>
<!-- 根据ID删除 -->
<delete id="deleteById" parameterType="Long">
DELETE FROM user WHERE id = #{id}
</delete>
<!-- 更新用户 -->
<update id="update" parameterType="com.example.usermanagement.entity.User">
UPDATE user
<set>
<if test="username != null">username = #{username},</if>
<if test="password != null">password = #{password},</if>
<if test="email != null">email = #{email},</if>
<if test="phone != null">phone = #{phone},</if>
<if test="status != null">status = #{status},</if>
<if test="score != null">score = #{score},</if>
</set>
WHERE id = #{id}
</update>
<!-- 根据ID查询 -->
<select id="selectById" parameterType="Long" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM user
WHERE id = #{id}
</select>
<!-- 根据用户名查询 -->
<select id="selectByUsername" parameterType="String" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM user
WHERE username = #{username}
</select>
<!-- 查询所有用户 -->
<select id="selectAll" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM user
ORDER BY id DESC
</select>
<!-- 分页查询 -->
<select id="selectByPage" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM user
ORDER BY id DESC
LIMIT #{offset}, #{limit}
</select>
<!-- 统计总数 -->
<select id="count" resultType="int">
SELECT COUNT(*) FROM user
</select>
<!-- 根据邮箱查询用户 -->
<select id="selectByEmail" parameterType="String" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM user
WHERE email = #{email}
</select>
<!-- 根据用户名搜索(模糊查询) -->
<select id="searchByUsername" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM user
WHERE username LIKE CONCAT('%', #{username}, '%')
ORDER BY id DESC
LIMIT #{offset}, #{limit}
</select>
<!-- 统计搜索结果数量 -->
<select id="countByUsername" resultType="int">
SELECT COUNT(*)
FROM user
WHERE username LIKE CONCAT('%', #{username}, '%')
</select>
</mapper>
1. Mapper接口设计分析
1.1 接口声明与注解
@Mapper
public interface UserMapper {
// 方法定义
}
@Mapper注解详解:
- MyBatis标识:告诉Spring这是MyBatis的Mapper接口
- 自动代理:Spring自动创建接口的实现类
- 依赖注入:可以被@Autowired注入到Service中
- 类型安全:编译时检查方法签名
接口vs实现类:
// 传统DAO实现
public class UserDaoImpl implements UserDao {
// 需要手写JDBC代码
}
// MyBatis Mapper
public interface UserMapper {
// 只需要定义方法签名,XML中写SQL
}
1.2 方法命名规范
// 查询类方法
User selectById(Long id);
User selectByUsername(String username);
List<User> selectAll();
// 插入类方法
int insert(User user);
// 更新类方法
int update(User user);
// 删除类方法
int deleteById(Long id);
// 统计类方法
int count();
命名约定分析:
- select:查询操作,返回实体或集合
- insert:插入操作,返回影响行数
- update:更新操作,返回影响行数
- delete:删除操作,返回影响行数
- count:统计操作,返回数量
1.3 参数传递设计
// 单个参数(MyBatis自动处理)
User selectById(Long id);
// 多个参数(使用@Param注解)
List<User> selectByPage(@Param("offset") Integer offset,
@Param("limit") Integer limit);
// 复杂对象参数
int insert(User user);
@Param注解作用:
- 参数命名:在XML中可以通过名称引用参数
- 多参数支持:避免MyBatis的参数0、参数1命名
- 可读性增强:XML中的参数名更有意义
2. XML映射文件结构解析
2.1 文件头声明
<?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">
作用说明:
- XML声明:指定版本和编码
- DTD约束:定义XML文件的结构规范
- MyBatis验证:确保XML语法正确
2.2 命名空间配置
<mapper namespace="com.example.usermanagement.mapper.UserMapper">
namespace重要性:
- 接口绑定:必须与Mapper接口全限定名一致
- 方法映射:XML中的SQL语句与接口方法一一对应
- 避免冲突:不同Mapper的同名方法不会冲突
3. 结果映射深度解析
3.1 ResultMap配置
<resultMap id="BaseResultMap" type="com.example.usermanagement.entity.User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="email" property="email"/>
<result column="phone" property="phone"/>
<result column="status" property="status"/>
<result column="score" property="score"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
ResultMap核心作用:
字段映射:
- 数据库字段 → Java属性
create_time
→createTime
(下划线转驼峰)update_time
→updateTime
标签区别:
<id>
:主键字段,MyBatis优化处理<result>
:普通字段映射
类型映射:
type="com.example.usermanagement.entity.User"
- 指定返回的Java对象类型
- MyBatis自动创建对象并设置属性
3.2 字段列表复用
<sql id="Base_Column_List">
id, username, password, email, phone, status, score, create_time, update_time
</sql>
设计优势:
- DRY原则:避免重复定义字段列表
- 维护性:字段变更只需修改一处
- 可读性:SQL语句更简洁
使用方式:
<select id="selectById" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM user
WHERE id = #{id}
</select>
4. SQL语句详细分析
4.1 插入语句设计
<insert id="insert" parameterType="com.example.usermanagement.entity.User"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (username, password, email, phone, status, score)
VALUES (#{username}, #{password}, #{email}, #{phone}, #{status}, #{score})
</insert>
核心配置解析:
useGeneratedKeys=“true”:
- 自动生成主键:数据库自增ID
- 回写主键:插入后ID自动设置到对象
- 便于后续操作:可以直接使用生成的ID
keyProperty=“id”:
- 指定主键属性:告诉MyBatis将生成的主键值设置给哪个属性
- 对象更新:插入后User对象的id字段会被自动设置
参数绑定:
#{username}, #{password}, #{email}
- 预编译SQL:防止SQL注入
- 类型转换:自动处理Java类型到数据库类型的转换
- 空值处理:null值自动处理
使用效果:
User user = new User();
user.setUsername("testuser");
user.setPassword("123456");
userMapper.insert(user);
// 插入后,user.getId() 会自动获得生成的主键值
System.out.println("生成的ID: " + user.getId());
4.2 动态更新语句
<update id="update" parameterType="com.example.usermanagement.entity.User">
UPDATE user
<set>
<if test="username != null">username = #{username},</if>
<if test="password != null">password = #{password},</if>
<if test="email != null">email = #{email},</if>
<if test="phone != null">phone = #{phone},</if>
<if test="status != null">status = #{status},</if>
<if test="score != null">score = #{score},</if>
</set>
WHERE id = #{id}
</update>
动态SQL优势:
<set>
标签作用:
- 智能组装:自动处理SET子句
- 逗号处理:自动去除末尾多余的逗号
- 条件更新:只更新有值的字段
<if>
条件判断:
<if test="username != null">username = #{username},</if>
- 空值检查:只有非null字段才会被更新
- 灵活更新:支持部分字段更新
- 避免覆盖:不会将现有数据置为null
生成的SQL示例:
-- 如果只更新用户名和邮箱
UPDATE user SET username = ?, email = ? WHERE id = ?
-- 如果只更新状态
UPDATE user SET status = ? WHERE id = ?
4.3 分页查询实现
<select id="selectByPage" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM user
ORDER BY id DESC
LIMIT #{offset}, #{limit}
</select>
MySQL分页语法:
- LIMIT offset, limit:MySQL特有语法
- offset:跳过的记录数
- limit:返回的记录数
分页计算:
// 第1页,每页10条:LIMIT 0, 10
// 第2页,每页10条:LIMIT 10, 10
// 第3页,每页10条:LIMIT 20, 10
Integer offset = (pageNum - 1) * pageSize;
排序策略:
ORDER BY id DESC
- ID倒序:最新数据在前
- 稳定排序:确保分页结果一致性
4.4 模糊搜索实现
<select id="searchByUsername" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM user
WHERE username LIKE CONCAT('%', #{username}, '%')
ORDER BY id DESC
LIMIT #{offset}, #{limit}
</select>
LIKE查询分析:
CONCAT函数:
- 字符串拼接:MySQL的CONCAT函数
- 防止SQL注入:参数化查询
- 跨数据库:不同数据库有不同语法
不同数据库的写法:
<!-- MySQL -->
WHERE username LIKE CONCAT('%', #{username}, '%')
<!-- Oracle -->
WHERE username LIKE '%' || #{username} || '%'
<!-- SQL Server -->
WHERE username LIKE '%' + #{username} + '%'
性能考虑:
-- 前置通配符影响索引
WHERE username LIKE '%zhang%' -- 不能使用索引
-- 后置通配符可以使用索引
WHERE username LIKE 'zhang%' -- 可以使用索引
4.5 统计查询
<select id="count" resultType="int">
SELECT COUNT(*) FROM user
</select>
<select id="countByUsername" resultType="int">
SELECT COUNT(*)
FROM user
WHERE username LIKE CONCAT('%', #{username}, '%')
</select>
resultType vs resultMap:
- resultType=“int”:直接返回基本类型
- resultMap:返回复杂对象类型
- 自动转换:MyBatis自动处理类型转换
5. 参数传递机制
5.1 单参数传递
User selectById(Long id);
<select id="selectById" parameterType="Long" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM user
WHERE id = #{id}
</select>
单参数特点:
- 自动识别:MyBatis自动识别参数类型
- 直接引用:XML中直接使用
#{参数名}
- parameterType可选:通常可以省略
5.2 多参数传递
List<User> selectByPage(@Param("offset") Integer offset,
@Param("limit") Integer limit);
<select id="selectByPage" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM user
ORDER BY id DESC
LIMIT #{offset}, #{limit}
</select>
@Param注解必要性:
// 不使用@Param(不推荐)
List<User> selectByPage(Integer offset, Integer limit);
// XML中需要使用:#{param1}, #{param2} 或 #{0}, #{1}
// 使用@Param(推荐)
List<User> selectByPage(@Param("offset") Integer offset,
@Param("limit") Integer limit);
// XML中可以使用:#{offset}, #{limit}
5.3 对象参数传递
int insert(User user);
<insert id="insert" parameterType="com.example.usermanagement.entity.User">
INSERT INTO user (username, password, email, phone, status, score)
VALUES (#{username}, #{password}, #{email}, #{phone}, #{status}, #{score})
</insert>
对象属性访问:
- 直接访问:
#{username}
等价于user.getUsername()
- 嵌套对象:
#{address.city}
访问嵌套属性 - 类型安全:编译时检查属性是否存在
6. MyBatis配置优化
6.1 application.yml中的MyBatis配置
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.usermanagement.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
配置说明:
- mapper-locations:XML文件位置
- type-aliases-package:实体类包路径
- map-underscore-to-camel-case:自动驼峰转换
- log-impl:SQL执行日志
6.2 自动驼峰转换效果
// 数据库字段 → Java属性
create_time → createTime
update_time → updateTime
user_name → userName
开启前后对比:
<!-- 未开启驼峰转换 -->
<resultMap id="UserResultMap" type="User">
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<!-- 开启驼峰转换后 -->
<!-- 可以省略ResultMap,MyBatis自动映射 -->
<select id="selectById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
7. 性能优化要点
7.1 索引使用建议
-- 为常用查询字段建索引
CREATE INDEX idx_username ON user(username);
CREATE INDEX idx_email ON user(email);
CREATE INDEX idx_status ON user(status);
-- 复合索引
CREATE INDEX idx_status_create_time ON user(status, create_time);
7.2 分页性能优化
<!-- 大数据量分页优化 -->
<select id="selectByPageOptimized" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM user
WHERE id > #{lastId}
ORDER BY id
LIMIT #{limit}
</select>
游标分页 vs 传统分页:
-- 传统分页(深分页性能差)
SELECT * FROM user ORDER BY id LIMIT 100000, 10;
-- 游标分页(性能稳定)
SELECT * FROM user WHERE id > 100000 ORDER BY id LIMIT 10;
7.3 批量操作支持
<!-- 批量插入 -->
<insert id="batchInsert" parameterType="list">
INSERT INTO user (username, password, email)
VALUES
<foreach collection="list" item="user" separator=",">
(#{user.username}, #{user.password}, #{user.email})
</foreach>
</insert>
<!-- 批量删除 -->
<delete id="batchDelete" parameterType="list">
DELETE FROM user WHERE id IN
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
8. 常见问题与解决方案
8.1 SQL注入防护
<!-- 安全的参数绑定 -->
WHERE username = #{username} <!-- 推荐:预编译SQL -->
<!-- 危险的字符串拼接 -->
WHERE username = '${username}' <!-- 不推荐:SQL注入风险 -->
#{}与${}区别:
- #{}:预编译参数,防SQL注入
- ${}:字符串替换,有注入风险
8.2 空值处理
<update id="update">
UPDATE user
<set>
<if test="username != null and username != ''">
username = #{username},
</if>
<if test="email != null and email != ''">
email = #{email},
</if>
</set>
WHERE id = #{id}
</update>
8.3 数据库兼容性
<!-- MySQL -->
<select id="selectByPage" resultMap="BaseResultMap">
SELECT * FROM user LIMIT #{offset}, #{limit}
</select>
<!-- Oracle -->
<select id="selectByPage" resultMap="BaseResultMap">
SELECT * FROM (
SELECT ROWNUM rn, t.* FROM user t WHERE ROWNUM <= #{offset} + #{limit}
) WHERE rn > #{offset}
</select>
9. 高级特性应用
9.1 动态SQL复杂示例
<select id="searchUsers" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM user
<where>
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
<if test="status != null">
AND status = #{status}
</if>
<if test="minScore != null">
AND score >= #{minScore}
</if>
<if test="maxScore != null">
AND score <= #{maxScore}
</if>
</where>
ORDER BY
<choose>
<when test="sortBy == 'score'">score DESC</when>
<when test="sortBy == 'createTime'">create_time DESC</when>
<otherwise>id DESC</otherwise>
</choose>
</select>
9.2 结果集嵌套
<!-- 一对多关联查询 -->
<resultMap id="UserWithOrdersMap" type="User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<collection property="orders" ofType="Order">
<id column="order_id" property="id"/>
<result column="order_amount" property="amount"/>
</collection>
</resultMap>
- 接口简洁:只需定义方法签名
- SQL分离:业务逻辑与SQL解耦
- 类型安全:编译时检查
- 动态SQL:灵活的条件查询
- 结果映射:自动对象转换
- 参数绑定:防SQL注入
预编译SQL:性能优化
结果缓存:二级缓存支持
批量操作:减少数据库交互
分页查询:大数据量处理
XML配置:SQL变更无需重编译
代码复用:SQL片段共享
规范统一:标准化的CRUD操作
易于测试:接口便于Mock测试