Mybatis映射文件解析
# Mybatis 映射文件解析
# mybatis 中比较符号的写法
# 第一种写法
直接在外层嵌套 <![CDATA[ ]]>
这个标签,例如 <![CDATA[ >= ]]>
标签说明:
CDATA
(Character Data)标签,它在 XML 和 HTML 中用于定义一段文本数据,该数据不应被解析器解析为标记。CDATA 部分中的内容将原样输出,即其中的所有字符都将被视为普通文本,即使它们可能包含特殊字符或标记。
sql 举例如下:
select
*
from
user
where
age <![CDATA[ >= ]]> 18
2
3
4
5
6
# 第二种写法
符号替换,如下面表格所示
原符号 | 替换符号 |
---|---|
< | < |
<= | <= |
> | > |
>= | >= |
& | & |
' | &apos |
" | " |
符号区分:
- 小于号 < --
<
-- 其中 l 代表 less,t 代表 than,合起来是less than
- 大于号 > --
>
-- 其中 g 代表 greater,t 代表 than,合起来是greater than
sql 举例如下:
select
*
from
user
where
age >= 18
2
3
4
5
6
# 顶级元素
SQL 映射文件有以下几个顶级元素(按照它们应该被定义的顺序):
cache
-- 给定命名空间的缓存配置cache-ref
-- 其他命名空间缓存配置的引用resultMap
-- 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象sql
-- 可被其他语句引用的可重用语句块insert
-- 映射插入语句update
-- 映射更新语句delete
-- 映射删除语句select
-- 映射查询语句
# 查询技巧
# 获取参数值的两种方式
有两种形式:
${}
-- 字符串替换#{}
-- 预编译处理
区别:
#{}
是预编译处理,像传进来的数据会加个''
(#
将传入的数据都当成一个字符串,会对自动传入的数据加一个单引号)- 会按照预编译SQL语句(PreparedStatement)的方式来处理这些占位符,即将参数绑定到SQL语句中的问号(
?
)位置上,而不是直接将参数值拼接到SQL语句中 - 对于数字类型的参数不会额外加引号,对于字符串类型则会加上单引号或者双引号
- 会按照预编译SQL语句(PreparedStatement)的方式来处理这些占位符,即将参数绑定到SQL语句中的问号(
${}
就是字符串替换。直接替换掉占位符。$
方式一般用于传入数据库对象,例如传入表名。使用${}
的话会导致 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>
2
3
# 新增返回主键 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)
);
2
3
4
5
在你的 MyBatis 映射文件中,你可以这样写:
<insert id="insertUser" parameterType="User">
INSERT INTO your_table (other_column) VALUES (#{otherColumn})
</insert>
2
3
然后,在你的 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
3
4
5
6
7
8
# 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>
2
3
4
5
6
7
8
在这个例子中,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();
2
3
4
5
6
7
8
# 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);
}
2
3
4
5
在这个例子中,
@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();
2
3
4
5
6
7
8
# 查询一条数据为 map 集合
1、Mapper 接口
/**
* 根据用户id查询用户信息为map集合
* @param id
* @return
*/
Map<String, Object> getUserToMap(@Param("id") int id);
2
3
4
5
6
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}-->
2
3
4
5
# 查询多条数据为 map 集合
# 方法一
1、Mapper 接口
/**
* 查询所有用户信息为map集合
* <p>
* 将表中的数据以map集合的方式查询,一条数据对应一个map;若有多条数据,就会产生多个map集合,
* 此时可以将这些map放在一个list集合中获取
*
* @return
*/
List<Map<String, Object>> getAllUserToMap();
2
3
4
5
6
7
8
9
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=张三}
}
-->
2
3
4
5
6
7
8
9
10
11
12
# 方法二
1、Mapper 接口(用 @MapKey
注解)
/**
* 查询所有用户信息为map集合
* <p>
* 将表中的数据以map集合的方式查询,一条数据对应一个map;若有多条数据,就会产生多个map集合,并且最终要以一个map的方式返回数据,
* 此时需要通过 @MapKey 注解设置 map 集合的键,值是每条数据所对应的 map 集合
*
* @return
*/
@MapKey("id")
Map<String, Object> getAllUserToMap();
2
3
4
5
6
7
8
9
10
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=张三}
}
-->
2
3
4
5
6
7
8
9
10
11
12
# 动态 sql
MyBatis 动态 SQL 提供了多种标签,用于在运行时动态构建 SQL 语句,极大地提高了 SQL 语句的灵活性和可重用性。
以下是几个关键动态 SQL 标签及其用法的总结:
# 1、if
含义
用于根据表达式的值来决定是否包含某段 SQL 语句。
用法
<if test="condition">
SQL 片段
</if>
2
3
解释
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>
2
3
4
5
6
# 3、where
含义
用于动态地添加 WHERE 子句,避免不必要的 AND
或 OR
关键词出现在 SQL 语句中。
用法
包裹多条可能的条件语句,MyBatis 会智能地忽略那些条件未满足(表达式结果为假)时产生的 AND
或 OR
关键词。
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>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 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>
2
3
4
5
6
7
8
9
10
11
12
13
14
解释
在更新数据的时候,使用 <set>
标签,使用了 set 标签会自动帮你删除尾部的逗号。
# 5、trim
含义
用来【删除】SQL 片段首尾的特定字符或关键字,也可以在首尾【添加】字符或关键字。
常用属性
prefix
:在 trim 标签中的内容的前面【添加】某些内容suffix
:在 trim 标签中的内容的后面【添加】某些内容prefixOverrides
:在 trim 标签中的内容的前面【去掉】某些内容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>
<!-- 另一种写法 -->
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 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>
2
3
4
5
6
7
上述示例为批量插入语句,会遍历名为 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>
2
3
4
# 模糊查询的 5 种方式
# 方式一:&{}
&{}
这种方式,简单,但是无法防止 SQL 注入,所以不推荐使用
<if test="dto.name != null">
AND name LIKE '%${dto.name}%'
</if>
2
3
# 方式二:#{}
在 #{}
左右两边写上字符串 "%"
<if test="dto.name != null">
AND name LIKE "%" #{dto.name} "%"
</if>
2
3
# 方式三:字符串拼接函数
用 concat()
函数
<if test="dto.name != null">
AND name LIKE concat('%', #{dto.name}, '%')
</if>
2
3
# 方式四: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>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 方式五:java 代码里写
1、直接在 java 代码里写,在值的两边加上 %
dto.setName("%张三%");
2、然后,在映射文件中直接传参就行
<if test="dto.name != null">
AND name LIKE #{dto.name}
</if>
2
3
# 延迟加载
# <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>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 结果对象示例
/**
* 人员信息视图
*/
@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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 写法分析
- 在业务实现类层(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 >= 3
</otherwise>
</choose>
</if>
# ...
2
3
4
5
6
7
8
9
10
11
12
13
14
15