Mybatis映射文件解析

Mybatis 映射文件解析

mybatis 中比较符号的写法

第一种写法

直接在外层嵌套 <![CDATA[ ]]> 这个标签,例如 <![CDATA[ >= ]]>

标签说明:

CDATA(Character Data)标签,它在 XML 和 HTML 中用于定义一段文本数据,该数据不应被解析器解析为标记。CDATA 部分中的内容将原样输出,即其中的所有字符都将被视为普通文本,即使它们可能包含特殊字符或标记。

sql 举例如下:

select 
	*
from 
	user
where 
	age <![CDATA[ >= ]]> 18

第二种写法

符号替换,如下面表格所示

原符号替换符号
<&lt;
<=&lt;=
>&gt;
>=&gt;=
&&amp;
&apos
"&quot

符号区分:

  • 小于号 < – &lt; – 其中 l 代表 less,t 代表 than,合起来是 less than
  • 大于号 > – &gt; – 其中 g 代表 greater,t 代表 than,合起来是 greater than

sql 举例如下:

select 
	*
from 
	user
where 
	age &gt;= 18

顶级元素

SQL 映射文件有以下几个顶级元素(按照它们应该被定义的顺序):

  1. cache – 给定命名空间的缓存配置
  2. cache-ref – 其他命名空间缓存配置的引用
  3. resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象
  4. sql – 可被其他语句引用的可重用语句块
  5. insert – 映射插入语句
  6. update – 映射更新语句
  7. delete – 映射删除语句
  8. select – 映射查询语句

查询技巧

获取参数值的两种方式

有两种形式:

  1. ${} – 字符串替换
  2. #{} – 预编译处理

区别:

  1. #{} 是预编译处理,像传进来的数据会加个 ''(# 将传入的数据都当成一个字符串,会对自动传入的数据加一个单引号)
    • 会按照预编译SQL语句(PreparedStatement)的方式来处理这些占位符,即将参数绑定到SQL语句中的问号(?)位置上,而不是直接将参数值拼接到SQL语句中
    • 对于数字类型的参数不会额外加引号,对于字符串类型则会加上单引号或者双引号
  2. ${} 就是字符串替换。直接替换掉占位符。$ 方式一般用于传入数据库对象,例如传入表名。使用 ${} 的话会导致 sq| 注入

什么是 SQL 注入呢?

比如:

select * from user where id = ${value}

value 应该是一个数值吧。然后如果对方传过来的是 001 and name= tom。这样不就相当于多加了一个条件嘛?

把 SQL 语句直接写进来了。如果是攻击性的语句呢?001;drop table user直接把表给删了

建议

所以为了防止 SQL 注入,能用 #{} 的不要去用 ${}

如果非要用 ${} 的话,那要注意防止 SQL 注入问题,可以手动判定传入的变量,进行过滤,一般 SQL 注入会输入很长的一条 SQL 语句。

动态设置表名

只能使用 ${},因为表名不能加单引号

例如:

<select id="getUserByTable" resultType="User">
	select * from t_info_${tableName}
</select>

新增返回主键 id⭐️

在 MyBatis 中,当你插入一条新记录并希望获取这条新记录的主键 ID 时,你可以使用数据库提供的自动生成主键的机制(例如自增字段),也可以通过 MyBatis 提供的一些特性来获取主键值。

以下是一些常见的方法来获取新增记录的主键 ID:

1. 使用数据库自增主键

这是最常见的方法。许多数据库系统(如 MySQL、PostgreSQL、SQL Server 等)都支持自动生成主键。在你的数据库表中,你可以设置一个字段为自增(例如,在 MySQL 中使用 AUTO_INCREMENT 属性),这样每次插入新记录时,数据库会自动为这个字段生成一个新的值。

你的表结构可能如下所示:

CREATE TABLE your_table (
    id INT NOT NULL AUTO_INCREMENT,
    other_column VARCHAR(255),
    PRIMARY KEY (id)
);

在你的 MyBatis 映射文件中,你可以这样写:

<insert id="insertUser" parameterType="User">
    INSERT INTO your_table (other_column) VALUES (#{otherColumn})
</insert>

然后,在你的 Java 代码中,你可以在插入记录后从数据库获取主键 ID:

在调用插入方法时,会自动将生成的主键 ID 设置到实体对象中,因此可以直接通过实体对象获取主键 ID。

User user = new User();
user.setOtherColumn("some value");
// sqlSession.insert("insertUser", user);
// sqlSession.insert("com.xxx.project.mapper.XxxMapper.insertUser", user);
userMapper.insertUser(user);

// 获取数据库自动生成的主键 ID
Long id = user.getId();
2. 使用 MyBatis 的 useGeneratedKeys 属性

如果你的数据库支持返回生成的主键,你可以在 MyBatis 映射文件中设置 useGeneratedKeys="true",这样 MyBatis 会在执行插入操作后返回生成的主键。

<insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO your_table (other_column) VALUES (#{otherColumn})
</insert>

# <insert id="insertEntity" parameterType="YourEntityClass" useGeneratedKeys="true" keyProperty="id">
#    INSERT INTO your_table (column1, column2, ...)
#    VALUES (#{property1}, #{property2}, ...)
# </insert>

在这个例子中,keyProperty 属性指定了要将生成的主键值设置到哪个属性上。

  • useGeneratedKeys="true":表示使用数据库自动生成的主键。
  • keyProperty="id":表示将自动生成的主键值设置到实体类的指定属性中。

在你的 Java 代码中,你可以直接获取这个属性的值:

User user = new User();
user.setOtherColumn("some value");
// sqlSession.insert("insertUser", user);
// sqlSession.insert("com.xxx.project.mapper.XxxMapper.insertUser", user);
userMapper.insertUser(user);

// 现在 user 对象的 id 属性已经被设置为新生成的主键值
Long id = user.getId();
3. 使用 MyBatis 的 @SelectKey 注解

如果你使用的是 MyBatis 注解而不是映射文件,你可以使用 @SelectKey 注解来获取生成的主键。

public interface UserMapper {
    @Insert("INSERT INTO your_table (other_column) VALUES (#{otherColumn})")
    @SelectKey(statement = "SELECT LAST_INSERT_ID()", keyProperty = "id", before = false)
    int insertUser(User user);
}

在这个例子中,

  • @SelectKey 注解指定了一个 SQL 语句 SELECT LAST_INSERT_ID() 来获取最后插入记录的 ID。
  • keyProperty 属性指定了要将这个值设置到哪个属性上。
  • before 属性指定了生成的主键是在执行插入操作之前还是之后被选择的。
User user = new User();
user.setOtherColumn("some value");
// sqlSession.insert("insertUser", user);
// sqlSession.insert("com.xxx.project.mapper.XxxMapper.insertUser", user);
userMapper.insertUser(user);

// 现在 user 对象的 id 属性已经被设置为新生成的主键值
Long id = user.getId();

查询一条数据为 map 集合

1、Mapper 接口

/**  
 * 根据用户id查询用户信息为map集合  
 * @param id  
 * @return  
 */  
Map<String, Object> getUserToMap(@Param("id") int id);

2、对应的映射文件

    <!--Map<String, Object> getUserToMap(@Param("id") int id);-->
    <select id="getUserToMap" resultType="map">
        select * from t_user where id = #{id}
    </select>
    <!--结果:{password=123456, sex=男, id=1, age=23, username=admin}-->

查询多条数据为 map 集合

方法一

1、Mapper 接口

    /**
     * 查询所有用户信息为map集合
     * <p>
     * 将表中的数据以map集合的方式查询,一条数据对应一个map;若有多条数据,就会产生多个map集合,
     * 此时可以将这些map放在一个list集合中获取
     *
     * @return
     */
    List<Map<String, Object>> getAllUserToMap();

2、对应的映射文件

    <!--Map<String, Object> getAllUserToMap();-->
    <select id="getAllUserToMap" resultType="map">
        select * from t_user
    </select>
    <!--
        结果:
        {
        1={password=123456, sex=男, id=1, age=23, username=admin},
        2={password=123456, sex=男, id=2, age=23, username=张三},
        3={password=123456, sex=男, id=3, age=23, username=张三}
        }
    -->
方法二

1、Mapper 接口(用 @MapKey 注解)

    /**
     * 查询所有用户信息为map集合
     * <p>
     * 将表中的数据以map集合的方式查询,一条数据对应一个map;若有多条数据,就会产生多个map集合,并且最终要以一个map的方式返回数据,
     * 此时需要通过 @MapKey 注解设置 map 集合的键,值是每条数据所对应的 map 集合
     *
     * @return
     */
    @MapKey("id")
    Map<String, Object> getAllUserToMap();

2、对应的映射文件

    <!--Map<String, Object> getAllUserToMap();-->
    <select id="getAllUserToMap" resultType="map">
        select * from t_user
    </select>
    <!--
        结果:
        {
        1={password=123456, sex=男, id=1, age=23, username=admin},
        2={password=123456, sex=男, id=2, age=23, username=张三},
        3={password=123456, sex=男, id=3, age=23, username=张三}
        }
    -->

动态 sql

MyBatis 动态 SQL 提供了多种标签,用于在运行时动态构建 SQL 语句,极大地提高了 SQL 语句的灵活性和可重用性。

以下是几个关键动态 SQL 标签及其用法的总结:

1、if

含义

用于根据表达式的值来决定是否包含某段 SQL 语句。

用法

    <if test="condition">
        SQL 片段
    </if>

解释

if 标签可通过 test 属性(即传递过来的数据)的表达式进行判断

  • 若表达式的结果为 true,则标签中的内容会执行;
  • 反之标签中的内容不会执行

2、choose / when / otherwise

含义

类似于 Java 中的 switch-case 结构,根据多个条件分支执行不同 SQL 片段。

用法

    <choose>
        <when test="condition1">SQL片段1</when>
        <when test="condition2">SQL片段2</when>
        <!-- 可以有多个when -->
        <otherwise>默认SQL片段(当所有when都不满足时执行)</otherwise>
    </choose>

3、where

含义

用于动态地添加 WHERE 子句,避免不必要的 ANDOR 关键词出现在 SQL 语句中。

用法

包裹多条可能的条件语句,MyBatis 会智能地忽略那些条件未满足(表达式结果为假)时产生的 ANDOR 关键词。

where 和 if 一般结合使用

  • 若 where 标签中的 if 条件都不满足,则 where 标签没有任何功能,即不会添加 where 关键字

  • 若 where 标签中的 if 条件满足,则 where 标签会自动添加 where 关键字,并将条件【最前方】多余的 and/or 去掉

注意:where 标签不能去掉【条件后】多余的 and/or

示例

    <!--List<Emp> getEmpByCondition(Emp emp);-->
    <select id="getEmpByCondition" resultType="Emp">
        select * from t_emp
        <where>
            <if test="empName != null and empName !=''">
                emp_name = #{empName}
            </if>
            <if test="age != null and age !=''">
                and age = #{age}
            </if>
            <if test="sex != null and sex !=''">
                and sex = #{sex}
            </if>
            <if test="email != null and email !=''">
                and email = #{email}
            </if>
        </where>
    </select>

4、set

含义

动态地添加 UPDATE 语句的 SET 部分,同样可以避免不必要的逗号问题。

用法

在 UPDATE 语句中,根据条件决定哪些列需要更新。(搭配 trim 标签使用。)

示例

    <update id="updateUserInfo">
        UPDATE
            t_user
        <set>
            <if test="dto.gender != null">
                gender = #{dto.gender},
            </if>
            <if test="dto.birthday != null">
                birthday = #{dto.birthday},
            </if>
        </set>
        WHERE
            user_id = #{userId}
    </update>

解释

在更新数据的时候,使用 <set> 标签,使用了 set 标签会自动帮你删除尾部的逗号

5、trim

含义

用来【删除】SQL 片段首尾的特定字符或关键字,也可以在首尾【添加】字符或关键字。

常用属性

  1. prefix:在 trim 标签中的内容的前面【添加】某些内容
  2. suffix:在 trim 标签中的内容的后面【添加】某些内容
  3. prefixOverrides:在 trim 标签中的内容的前面【去掉】某些内容
  4. suffixOverrides:在 trim 标签中的内容的后面【去掉】某些内容(suffixOverrides="and|or")

用法

		<trim prefix="(" suffix=")" prefixOverrides="," suffixOverrides=",">
  		column1,
  		column2
		</trim>
<!-- 上述示例会在括号内去除列名间的逗号,并在前后分别添加括号。 -->

    <update id="updateUserInfo">
        UPDATE
            t_user
        <set>
            <if test="dto.gender != null">
                gender = #{dto.gender},
            </if>
            <if test="dto.birthday != null">
                birthday = #{dto.birthday}
            </if>
            <trim suffixOverrides=","/>
        </set>
        WHERE
            user_id = #{userId}
    </update>
<!-- 上述示例会删除内容最后的逗号(处理SET部分末尾可能出现的多余逗号) -->

    <trim prefix="SET" suffixOverrides=",">
        ...
    </trim>
<!-- 另一种写法 -->

6、foreach

含义

遍历集合,适用于 in 语句或者批量插入等场景。

用法

    <insert id="batchInsert">
        INSERT INTO tbl_user (id,name,age,sex,is_delete)
        VALUES
        <foreach collection="userList" item="item" index="index" open="(" separator="),(" close=")">
            #{item.id},#{item.name},#{item.age},#{item.sex},#{item.isDelete}
        </foreach>
    </insert>

上述示例为批量插入语句,会遍历名为 userList 的集合,依次将每个元素 item 插入 SQL 语句中,每一条数据用 "),(" 分隔。

7、include

  • sql 片段,可以记录一段公共 sql 片段,在使用的地方通过 include 标签进行引入
  • 声明 sql 片段:<sql> 标签
<sql id="empColumns">eid,emp_name,age,sex,email</sql>
  • 引用 sql 片段:<include> 标签
<!--List<Emp> getEmpByCondition(Emp emp);-->
<select id="getEmpByCondition" resultType="Emp">
	select <include refid="empColumns"></include> from t_emp
</select>

模糊查询的 5 种方式

方式一:&{}

&{} 这种方式,简单,但是无法防止 SQL 注入,所以不推荐使用

<if test="dto.name != null"> 
	AND name LIKE '%${dto.name}%'
</if>

方式二:#{}

#{} 左右两边写上字符串 "%"

<if test="dto.name != null"> 
	AND name LIKE "%" #{dto.name} "%"
</if>

方式三:字符串拼接函数

concat() 函数

<if test="dto.name != null"> 
	AND name LIKE concat('%', #{dto.name}, '%')
</if>

方式四:bind 标签

<select id="searchstudents" resultType="com.example.entity.studentEntity"
        parameterType="com.example.entity.studentEntity">
  
    <bind name="pattern1" value="'%' + parameter.name + '%'" />
    <bind name="pattern2" value="'%' + parameter.address + '%'" />
  
    SELECT * FROM test student
    <where>
        <if test="name != null and name != ''">
            name LIKE #{pattern1}
        </if>
        <if test="address != null and address != ''">
            AND address LIKE #{pattern2}
        </if>
    </where>
    ORDER BY id
  
</select>

方式五:java 代码里写

1、直接在 java 代码里写,在值的两边加上 %

dto.setName("%张三%");

2、然后,在映射文件中直接传参就行

<if test="dto.name != null"> 
	AND name LIKE #{dto.name}
</if>

延迟加载

<collection> 标签

  • property: 指定父对象中的属性名,该属性应该是一个集合类型,如 List 或 Set。
  • ofType: 指定集合中元素的类型
  • select: 指定一个 MyBatis 查询,该查询返回集合中的元素。
  • column: 用于传递参数给 <select> 查询的参数名称。(例如:column="objId=id"id 是父对象的元素,objId 是查询参数 #{objId}
  • 延迟加载: <collection> 标签通常用于延迟加载关联集合。
  • 关联查询: 通过 <collection> 标签,MyBatis 可以处理关联查询,将多个表的结果集映射到一个对象的属性中。
  • 参数传递: column 属性用于将查询参数传递给关联查询。

写法示例

查询语句
    <resultMap id="dVO" type="com.chenmeng.project.vo.MaintainerVO">
        <collection property="documentInfoVoS"
                    ofType="com.chenmeng.project.vo.DocumentInfoVO" select="getDocumentInfo"
                    column="objId=id">
        </collection>
    </resultMap>

    <select id="getDocumentInfo" resultType="com.chenmeng.project.vo.DocumentInfoVO">
        select info.certificate_name name,
               expire_date           expire,
               file.file_path        image
        from t_test_document_info info
            left join t_test_file file on file.obj_id = info.id
            and file.is_deleted = 0
        where info.obj_id = #{objId}
          and info.is_deleted = 0
    </select>

    <select id="queryByCreateUser" resultMap="dVO">
        SELECT mn.id, mn.name
               en.enterprise_name,
               en.field_type
        FROM t_test_enterprise en,
             t_test_maintainer mn
        WHERE mn.is_deleted = 0
          and en.id = mn.enterprise_id
          and en.is_deleted = 0
          and mn.create_user = #{userId}
    </select>
结果对象示例
/**
 * 人员信息视图
 */
@Data
public class MaintainerVO implements Serializable {

  private static final long serialVersionUID = 1L;

  @ApiModelProperty(value = "维保人id")
  private Long id;

  @ApiModelProperty(value = "维保人姓名")
  private String name;

  @ApiModelProperty(value = "证件信息")
  private List<DocumentInfoVO> documentInfoVoS;
}

/**
 * 证件信息视图
 */
@Data
public class DocumentInfoVO implements Serializable {

  private Long id;

  private String name;

  private String expire;

  private String image;
}
写法分析
  • 在业务实现类层(impl),调用 queryByCreateUser 对应的 mapper 接口,最终返回 MaintainerVO 对象类型
  • <collection> 标签会延迟加载关联集合,自动查询 documentInfoVoS 属性的元素并加载。

<if> 标签判断条件等于字符串的值

写法示例

# ...
        <if test="req.yearCheckCount != null and req.yearCheckCount != ''">
          <choose>
              <when test="req.yearCheckCount == '0'.toString()">
                  and t2.year_check_count is null
              </when>
              <when test="req.yearCheckCount == '1'.toString() or req.yearCheckCount == '2'.toString()">
                  and t2.year_check_count = #{req.yearCheckCount}
              </when>
              <otherwise>
                  and t2.year_check_count &gt;= 3
              </otherwise>
          </choose>
        </if>
# ...

学习参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值