MyBatis持久层实现
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 userWHERE id = #{id}</select><!-- 根据用户名查询 --><select id="selectByUsername" parameterType="String" resultMap="BaseResultMap">SELECT <include refid="Base_Column_List"/>FROM userWHERE username = #{username}</select><!-- 查询所有用户 --><select id="selectAll" resultMap="BaseResultMap">SELECT <include refid="Base_Column_List"/>FROM userORDER BY id DESC</select><!-- 分页查询 --><select id="selectByPage" resultMap="BaseResultMap">SELECT <include refid="Base_Column_List"/>FROM userORDER BY id DESCLIMIT #{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 userWHERE email = #{email}</select><!-- 根据用户名搜索(模糊查询) --><select id="searchByUsername" resultMap="BaseResultMap">SELECT<include refid="Base_Column_List" />FROM userWHERE username LIKE CONCAT('%', #{username}, '%')ORDER BY id DESCLIMIT #{offset}, #{limit}</select><!-- 统计搜索结果数量 --><select id="countByUsername" resultType="int">SELECT COUNT(*)FROM userWHERE 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 userWHERE 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 userORDER BY id DESCLIMIT #{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 userWHERE username LIKE CONCAT('%', #{username}, '%')ORDER BY id DESCLIMIT #{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 userWHERE 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 userWHERE 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 userORDER BY id DESCLIMIT #{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/*.xmltype-aliases-package: com.example.usermanagement.entityconfiguration:map-underscore-to-camel-case: truelog-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 idLIMIT #{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测试