MyBatis持久层实现

发布于:2025-08-17 ⋅ 阅读:(18) ⋅ 点赞:(0)

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_timecreateTime(下划线转驼峰)
  • update_timeupdateTime

标签区别:

  • <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>
  1. 接口简洁:只需定义方法签名
  2. SQL分离:业务逻辑与SQL解耦
  3. 类型安全:编译时检查
  4. 动态SQL:灵活的条件查询
  5. 结果映射:自动对象转换
  6. 参数绑定:防SQL注入
  • 预编译SQL:性能优化

  • 结果缓存:二级缓存支持

  • 批量操作:减少数据库交互

  • 分页查询:大数据量处理

  • XML配置:SQL变更无需重编译

  • 代码复用:SQL片段共享

  • 规范统一:标准化的CRUD操作

  • 易于测试:接口便于Mock测试


网站公告

今日签到

点亮在社区的每一天
去签到