MyBatis 框架

发布 | 2024-09-11 | JAVA,Mysql,JavaWeb

MyBatis

  • 一个优秀的持久层(Persistence Layer)框架,支持自定义 SQL、存储过程及高级映射。它主要用于 Java 应用程序中简化对数据库的操作。

    • 轻量级:MyBatis 比较轻量,核心功能集中在 SQL 映射上,不会强制要求全自动化的 ORM(Object Relational Mapping)。
    • 可控性:程序员可以完全掌控 SQL 的执行,适合需要定制复杂 SQL 语句的场景。
    • 灵活性:支持自定义 SQL、存储过程和高级映射,适合大多数数据库操作需求。
    • 相比于 Hibernate 等全自动 ORM 框架,MyBatis 将 SQL 写入 XML 文件中或直接使用注解来编写 SQL,允许更大的灵活性和控制力。

Hello World 示例

为了演示 MyBatis 的基本用法,以下是一个简单的 "Hello World" 示例,展示如何进行数据库操作。

基本步骤

  1. Dao 接口与 XML 配置文件:

    • 每个 Dao 接口(Data Access Object)都有一个对应的 XML 实现文件。
    • XML 文件用于定义 SQL 查询语句,并映射到接口的方法。
  2. 自动生成的代理对象:

    • MyBatis 会自动为 Dao 接口生成实现类,创建代理对象来执行 SQL 语句。
  3. XML 文件配置:

    • namespace: 必须与 Dao 接口的全类名一致,以便 MyBatis 能够将接口方法与 SQL 语句关联。
    • selectinsertupdatedelete 标签:用于定义相应的 SQL 操作。
    • CRUD 标签的 id: 必须与 Dao 接口的方法名一致,便于映射。

示例代码

Dao 接口 (UserMapper.java):

public interface UserMapper {
    // 根据用户ID查询用户
    User getUserById(int id);

    // 插入新用户
    void insertUser(User user);
}

XML 配置文件 (UserMapper.xml):

<mapper namespace="com.example.mapper.UserMapper">
    <!-- 查询用户 -->
    <select id="getUserById" resultType="com.example.model.User">
        SELECT * FROM users WHERE id = #{id}
    </select>

    <!-- 插入新用户 -->
    <insert id="insertUser">
        INSERT INTO users (name, age) VALUES (#{name}, #{age})
    </insert>
</mapper>

CRUD 操作

MyBatis 提供了丰富的 CRUD 操作(增删改查)功能,通过 Mapper 接口和 XML 文件中的 CRUD 标签来实现。

1 定义 Mapper 接口

在接口中定义方法,例如增删改查方法。

public interface UserMapper {
    // 查询用户
    User getUserById(int id);
    
    // 插入用户
    void insertUser(User user);
    
    // 更新用户
    void updateUser(User user);
    
    // 删除用户
    void deleteUser(int id);
}

2 定义 CRUD 标签

在 XML 文件中使用 selectinsertupdatedelete 标签分别表示对应的 SQL 操作。

<mapper namespace="com.example.mapper.UserMapper">
    <select id="getUserById" resultType="com.example.model.User">
        SELECT * FROM users WHERE id = #{id}
    </select>
    
    <insert id="insertUser">
        INSERT INTO users (name, age) VALUES (#{name}, #{age})
    </insert>
    
    <update id="updateUser">
        UPDATE users SET name = #{name}, age = #{age} WHERE id = #{id}
    </update>
    
    <delete id="deleteUser">
        DELETE FROM users WHERE id = #{id}
    </delete>
</mapper>

自增 ID

如果数据库中的某些表具有自增 ID(auto-increment ID),MyBatis 也可以进行相关配置。

XML 配置

在插入操作中,配置 useGeneratedKeyskeyProperty 来自动获取生成的主键。

<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO users (name, age) VALUES (#{name}, #{age})
</insert>

MyBatis 参数传递

1. #{}${} 的区别

MyBatis 提供了两种方式来传递参数:#{}${},它们的区别在于底层使用的方式和安全性。

  • #{}: 使用 PreparedStatement 方式,SQL 预编译后设置参数,避免了 SQL 注入风险。推荐优先使用。
  • ${}: 使用 Statement 方式,直接拼接参数,有可能造成 SQL 注入攻击。应尽量避免使用。

示例

安全的参数传递(推荐):

<select id="getUserByName" resultType="com.example.model.User">
    SELECT * FROM users WHERE name = #{name}
</select>

存在安全风险的参数传递:

<select id="getUserByNameUnsafe" resultType="com.example.model.User">
    SELECT * FROM users WHERE name = ${name}
</select>

2. 参数取值方式

  • 单个参数: 直接传递一个参数(无需特殊处理)。
  • 多个参数: 使用 @Param 注解指定参数名称。
List<User> getUsersByAgeAndGender(@Param("age") int age, @Param("gender") String gender);
  • 集合或对象参数: 可以直接传递集合或对象作为参数。
List<User> getUsersByIds(List<Integer> ids);

MyBatis 结果封装

在 MyBatis 中,不同类型的返回值有不同的处理方式

常见的返回值类型包括普通类型、对象类型、ListMap,以及通过自定义 ResultMap 进行复杂的结果映射。

返回值类型

普通类型和对象类型:
MyBatis 支持直接返回数据库中查询的单个字段结果(如 intString 等)。对于对象类型,查询结果会直接映射为 Java 对象类型,通常是实体类。

示例:返回普通类型

返回一个用户的姓名(String 类型):

<select id="getUserNameById" resultType="String">
    SELECT name FROM users WHERE id = #{id}
</select>
示例:返回对象类型

返回一个用户对象(User 类型):

<select id="getUserById" resultType="com.example.model.User">
    SELECT * FROM users WHERE id = #{id}
</select>
返回 List 类型:

返回一个用户的列表:

<select id="getAllUsers" resultType="com.example.model.User">
    SELECT * FROM users
</select>
返回 Map 类型:

MyBatis 可以将查询结果封装为 Map,常见的使用场景是需要将数据库中的数据映射为键值对。

示例:返回 Map

<select id="getUserMap" resultType="map">
    SELECT id, name, age FROM users
</select>

可以使用 @MapKey 注解将 Map 的键指定为某个字段:

@MapKey("id")
Map<Integer, User> getUserMap();

自定义结果集(ResultMap)

当数据库字段和 Java 对象的属性名称不一致,或查询结果需要映射到多个对象时,可以使用 ResultMap 进行自定义结果映射。

示例:定义 ResultMap

<resultMap id="userResultMap" type="com.example.model.User">
    <id property="id" column="user_id"/>
    <result property="name" column="user_name"/>
    <result property="age" column="user_age"/>
</resultMap>

<select id="getUserById" resultMap="userResultMap">
    SELECT user_id, user_name, user_age FROM users WHERE user_id = #{id}
</select>

MyBatis 关联查询

MyBatis 通过 ResultMapassociationcollection 标签来实现一对一和一对多的关联查询。

可以在查询过程中动态加载关联对象的数据,实现更加灵活的结果映射。

在开始关联查询之前,需要复习数据库中的关联关系(如一对一、一对多)并确保项目中正确配置 MyBatis 环境。

示例:环境配置

数据库中的用户表(User)和订单表(Order)之间存在一对多的关系。配置数据库连接信息和 Mapper 配置。

一对一关系封装 (association 标签)

一对一关系表示每个实体仅与另一个实体相关联。例如,每个用户(User)只有一个订单(Order)。在 MyBatis 中,可以使用 association 标签来处理一对一关系。

association 标签使用方法

原生查询方法

通过 SQL 的 JOIN 语句将两个表的数据连接起来,实现一对一关系的查询:

<select id="getUserWithOrder" resultType="com.example.model.User">
    SELECT u.user_id, u.user_name, o.order_id, o.order_date
    FROM users u
    JOIN orders o ON u.user_id = o.user_id
    WHERE u.user_id = #{id}
</select>

在上述例子中,JOIN 语句将用户表 (users) 和订单表 (orders) 连接起来,并查询出用户及其对应的订单信息。

分步查询方法

在分步查询中,主查询和关联查询是分开的,通过 association 标签实现关联对象的按需加载。

association 标签配置

<resultMap id="userResultMap" type="com.example.model.User">
    <id property="id" column="user_id"/>
    <result property="name" column="user_name"/>
    <association property="order" javaType="com.example.model.Order"
                 select="com.example.mapper.OrderMapper.getOrderById" column="user_id"/>
</resultMap>

配置解释:

  • property="order":指定关联的 Java 对象属性名为 order
  • javaType="com.example.model.Order":指定关联对象的类型为 Order
  • select="com.example.mapper.OrderMapper.getOrderById":指定分步查询的方法 getOrderById
  • column="user_id":指定作为参数传递的字段。

定义分步查询方法

OrderMapper.xml 中:

<select id="getOrderById" resultType="com.example.model.Order">
    SELECT order_id, order_date
    FROM orders
    WHERE user_id = #{user_id}
</select>

主查询使用分步查询:

<select id="getUserById" resultMap="userResultMap">
    SELECT user_id, user_name
    FROM users
    WHERE user_id = #{id}
</select>

一对多关系封装 (collection 标签)

一对多关系表示一个实体可以与多个其他实体相关联。例如,一个用户(User)可以有多个订单(Order)。在 MyBatis 中,可以使用 collection 标签来处理一对多关系。

collection 标签使用方法

原生查询方法

在原生 SQL 中,通过 JOIN 语句查询一个用户及其所有订单:

<select id="getUserWithOrders" resultType="com.example.model.User">
    SELECT u.user_id, u.user_name, o.order_id, o.order_date
    FROM users u
    LEFT JOIN orders o ON u.user_id = o.user_id
    WHERE u.user_id = #{id}
</select>

这里使用 LEFT JOIN 可以将用户和其所有订单数据查询出来。

分步查询方法

在分步查询中,通过 collection 标签动态加载关联对象的集合。

collection 标签配置

<resultMap id="userResultMap" type="com.example.model.User">
    <id property="id" column="user_id"/>
    <result property="name" column="user_name"/>
    <collection property="orders" ofType="com.example.model.Order"
                select="com.example.mapper.OrderMapper.getOrdersByUserId" column="user_id"/>
</resultMap>

配置解释:

  • property="orders":指定关联的 Java 集合属性名为 orders
  • ofType="com.example.model.Order":指定集合中每个元素的类型为 Order
  • select="com.example.mapper.OrderMapper.getOrdersByUserId":指定分步查询的方法 getOrdersByUserId
  • column="user_id":指定用于分步查询的参数列。

定义分步查询方法

OrderMapper.xml 中:

<select id="getOrdersByUserId" resultType="com.example.model.Order">
    SELECT order_id, order_date
    FROM orders
    WHERE user_id = #{user_id}
</select>

主查询使用分步查询:

<select id="getUserById" resultMap="userResultMap">
    SELECT user_id, user_name
    FROM users
    WHERE user_id = #{id}
</select>

动态 SQL

if 标签

if 标签用于在 SQL 语句中加入条件逻辑。根据指定的条件表达式,MyBatis 会判断是否将其包含的 SQL 片段放入最终的 SQL 中。

<select id="findUserByCondition" resultType="User">
    SELECT * FROM users
    <where>
        <if test="username != null">
            AND username = #{username}
        </if>
        <if test="age != null">
            AND age = #{age}
        </if>
    </where>
</select>
where 标签

where 标签用于自动处理 SQL 语句中的条件部分。如果条件为空,它不会添加 WHERE 关键字,同时会去掉多余的 ANDOR

<select id="findUserByCondition" resultType="User">
    SELECT * FROM users
    <where>
        <if test="username != null">
            username = #{username}
        </if>
        <if test="age != null">
            AND age = #{age}
        </if>
    </where>
</select>

where 标签会自动去掉前置的 ANDOR,并且仅在有条件时添加 WHERE 关键字。

set 标签

set 标签用于生成 UPDATE 语句中的 SET 子句。它自动去掉尾部的逗号(,)。

<update id="updateUser" parameterType="User">
    UPDATE users
    <set>
        <if test="username != null">username = #{username},</if>
        <if test="age != null">age = #{age},</if>
    </set>
    WHERE id = #{id}
</update>

set 标签会自动去掉 SET 子句中最后的逗号。

trim 标签

trim 标签是一个更通用的标签,允许自定义前缀和后缀,通常用来替代 where 标签。

<select id="findUserByCondition" resultType="User">
    SELECT * FROM users
    <trim prefix="WHERE" prefixOverrides="AND | OR">
        <if test="username != null">
            AND username = #{username}
        </if>
        <if test="age != null">
            AND age = #{age}
        </if>
    </trim>
</select>

trim 标签使用 prefix 属性指定前缀,用 prefixOverrides 去掉指定的多余前缀。

trim 标签同样可以用于 UPDATE 语句中的 SET 子句,用来替代 set 标签。

<update id="updateUser" parameterType="User">
    UPDATE users
    <trim prefix="SET" suffixOverrides=",">
        <if test="username != null">username = #{username},</if>
        <if test="age != null">age = #{age},</if>
    </trim>
    WHERE id = #{id}
</update>

trim 标签通过 suffixOverrides 属性去掉末尾的逗号。

分支选择

choosewhenotherwise 标签用于实现类似 switch-case 的逻辑选择。

<select id="findUserByStatus" resultType="User">
    SELECT * FROM users
    <where>
        <choose>
            <when test="status == 'active'">
                status = 'ACTIVE'
            </when>
            <when test="status == 'inactive'">
                status = 'INACTIVE'
            </when>
            <otherwise>
                status = 'UNKNOWN'
            </otherwise>
        </choose>
    </where>
</select>

choose 标签确保只有一个 when 条件成立,如果没有匹配项,使用 otherwise 子句。

foreach 标签

foreach 标签用于处理集合类型的参数(如 ListSet),通常用于 IN 查询。

<select id="findUsersByIds" resultType="User">
    SELECT * FROM users WHERE id IN
    <foreach collection="list" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

foreach 标签生成一组逗号分隔的值,常用于批量操作。

foreach 标签也可以用于批量插入操作。

<insert id="insertUsers" parameterType="list">
    INSERT INTO users (username, age) VALUES 
    <foreach collection="list" item="user" separator=",">
        (#{user.username}, #{user.age})
    </foreach>
</insert>

foreach 标签生成多条 VALUES 子句,实现批量插入。

foreach 标签还可以用于批量更新操作。

<update id="updateUsers" parameterType="list">
    <foreach collection="list" item="user" separator=";">
        UPDATE users
        <set>
            username = #{user.username},
            age = #{user.age}
        </set>
        WHERE id = #{user.id}
    </foreach>
</update>

每次迭代生成一条 UPDATE 语句,用于批量更新数据。

在 MyBatis 中,批量操作默认会在一个事务中执行,只有在所有操作都成功时才会提交,否则会回滚。需要配置 ExecutorType.BATCH 才能启用批处理模式。

<sql> 标签

<sql> 标签用于抽取可复用的 SQL 片段,<include> 标签用于引用这些片段。

<sql id="userColumns">id, username, age</sql>

<select id="findAllUsers" resultType="User">
    SELECT <include refid="userColumns" /> FROM users
</select>

sql 标签定义一个片段,include 标签引用这个片段。

标签
MyBatis

© 著作权归作者所有

本文由 趣代码Blog 创作,采用 知识共享署名4.0 国际许可协议进行许可,本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。

评论关闭