<think>我们被要求使用MyBatis的@Select注解来实现动态SQL模糊查询。根据引用[1]中的示例,我们可以使用`<script>`标签来编写动态SQL。在模糊查询中,我们通常使用LIKE语句,并且为了避免SQL注入,我们使用#{}来传递参数,同时使用concat函数来拼接通配符。
引用[1]中的示例:
@Select({
"<script>",
"SELECT * FROM user",
"WHERE 1=1",
"<when test='name!=null and name!=\"\"'>",
"AND name LIKE concat('%',#{name},'%')",
"</when>",
"</script>"
})
public List<User> getUserList(String name);
注意:这里使用了`<when>`标签来动态添加条件,当name不为null且不为空字符串时,才会添加该条件。
但是,我们也可以使用`<if>`标签,因为`<when>`通常用在`<choose>`内部,而单个条件判断用`<if>`即可。
因此,我们可以这样写:
```java
@Select({
"<script>",
"SELECT * FROM user",
"WHERE 1=1",
"<if test='name != null and name != \"\"'>",
"AND name LIKE CONCAT('%', #{name}, '%')",
"</if>",
"</script>"
})
List<User> getUserList(@Param("name") String name);
```
注意:这里使用了`@Param`注解来指定参数名,确保在XML中能够正确引用。但是,在注解中使用`@Param`是必须的,因为我们在动态SQL的test表达式中使用了`name`,所以需要将方法参数通过`@Param`命名为"name"。
另外,引用[2]中展示了另一种写法,但它是处理IN条件,与模糊查询不同,不过我们可以参考其动态SQL的结构。
如果不想使用`<script>`标签,MyBatis还提供了其他注解如`@SelectProvider`来编写更复杂的动态SQL,但根据要求,我们使用@Select注解并内嵌<script>的方式。
另外,注意在字符串中拼接SQL片段时,要确保语句的正确性。在Java字符串中,双引号需要转义,但上面的例子中已经使用了单引号包裹test表达式,所以内部的双引号使用转义(\"\")表示空字符串。
但是,在test表达式中,我们也可以使用单引号来表示字符串,这样内部的双引号就不需要转义了。例如:
test="name != null and name != ''"
所以,我们可以这样写,避免转义双引号:
```java
@Select({
"<script>",
"SELECT * FROM user",
"WHERE 1=1",
"<if test=\"name != null and name != ''\">", // 这里使用双引号包裹整个test表达式,那么内部字符串用单引号
"AND name LIKE CONCAT('%', #{name}, '%')",
"</if>",
"</script>"
})
List<User> getUserList(@Param("name") String name);
```
或者,为了在Java字符串中避免转义,我们也可以将整个test表达式用单引号包裹,内部字符串用双引号(但内部字符串为空时,用两个单引号表示空字符串可能会引起混淆,所以通常用转义双引号表示空字符串,或者内部字符串用单引号):
推荐写法(使用单引号包裹test表达式,内部字符串用双引号):
test='name != null and name != ""'
但是,在Java字符串中,双引号不需要转义,所以我们可以这样写:
```java
@Select({
"<script>",
"SELECT * FROM user",
"WHERE 1=1",
"<if test='name != null and name != \"\"'>", // 这里用单引号包裹test表达式,内部双引号需要转义
"AND name LIKE CONCAT('%', #{name}, '%')",
"</if>",
"</script>"
})
List<User> getUserList(@Param("name") String name);
```
或者,内部字符串用单引号表示空字符串(注意:在MyBatis的动态SQL中,空字符串可以用''表示,所以我们可以这样写):
```java
@Select({
"<script>",
"SELECT * FROM user",
"WHERE 1=1",
"<if test='name != null and name != \"\"'>", // 或者使用两个单引号表示空字符串,但注意在Java字符串中,两个连续的单引号是允许的,但这样写可能不太清晰,所以通常用转义双引号
// 或者这样写:<if test='name != null and name != ""'> 但是这样在Java字符串中,双引号不需要转义,所以可以直接写,但外部用单引号包裹,内部双引号不会冲突。
// 实际上,在外部单引号包裹的字符串中,内部的双引号不需要转义,所以可以写成:<if test='name != null and name != ""'>
"AND name LIKE CONCAT('%', #{name}, '%')",
"</if>",
"</script>"
})
List<User> getUserList(@Param("name") String name);
```
但是,为了避免混淆,我们通常采用外部用双引号,内部字符串用单引号的方式,并且对双引号进行转义(如第一种方式),或者外部用单引号,内部字符串用双引号(但需要转义内部的双引号,因为内部双引号在Java字符串中需要转义)。这看起来有点混乱,我们选择一种清晰的方式。
实际中,我们经常看到这样的写法:
```java
@Select({
"<script>",
"SELECT * FROM user",
"WHERE 1=1",
"<if test=\"name != null and name != ''\">", // 外部双引号,内部字符串用单引号(表示空字符串)
"AND name LIKE CONCAT('%', #{name}, '%')",
"</if>",
"</script>"
})
List<User> getUserList(@Param("name") String name);
```
这种写法在Java字符串中,双引号包裹整个test表达式,内部字符串用单引号,这样空字符串就是'',不需要转义,且清晰。
总结:推荐使用外部双引号,内部字符串用单引号,这样在Java字符串中不需要对双引号进行转义,且内部单引号不会与外部冲突。
另外,注意:在MySQL中,CONCAT函数可以拼接多个字符串,这里我们拼接了'%'、参数和'%',形成%参数%的格式,即前后模糊匹配。
如果数据库是Oracle,则可以使用`'%' || #{name} || '%'`,但这里假设是MySQL。
因此,最终代码为:
```java
@Select({
"<script>",
"SELECT * FROM user",
"WHERE 1=1",
"<if test=\"name != null and name != ''\">",
" AND name LIKE CONCAT('%', #{name}, '%')",
"</if>",
"</script>"
})
List<User> getUserList(@Param("name") String name);
```
注意:这里使用了`WHERE 1=1`来避免判断第一个条件时出现AND问题,也可以使用`<where>`标签来动态处理WHERE关键字,但这里为了简单使用了1=1。
使用`<where>`标签的写法:
```java
@Select({
"<script>",
"SELECT * FROM user",
"<where>",
" <if test=\"name != null and name != ''\">",
" name LIKE CONCAT('%', #{name}, '%')",
" </if>",
"</where>",
"</script>"
})
List<User> getUserList(@Param("name") String name);
```
这样,当没有条件时,WHERE关键字不会出现;有条件时,WHERE关键字会自动加上,并且会处理第一个条件前的AND或OR(去掉或保留)。但是注意,在`<where>`标签内部,如果条件前面有AND或OR,MyBatis会在拼接时自动去掉第一个条件前的AND或OR。所以,如果我们在条件中写了AND,那么它可能被去掉,导致语法错误。因此,在`<where>`标签内部的条件中,不应该在开头添加AND或OR。
但是,如果只有一个条件,且写在`<where>`标签内,那么直接写条件即可,不需要AND。如果有多个条件,则第一个条件前不能有AND,后面的条件前要有AND。
所以,如果我们只有一个条件,可以这样写:
```java
@Select({
"<script>",
"SELECT * FROM user",
"<where>",
" <if test=\"name != null and name != ''\">",
" name LIKE CONCAT('%', #{name}, '%')",
" </if>",
"</where>",
"</script>"
})
List<User> getUserList(@Param("name") String name);
```
如果有多个条件,例如还要根据年龄查询:
```java
@Select({
"<script>",
"SELECT * FROM user",
"<where>",
" <if test=\"name != null and name != ''\">",
" name LIKE CONCAT('%', #{name}, '%')",
" </if>",
" <if test=\"age != null\">",
" AND age = #{age}",
" </if>",
"</where>",
"</script>"
})
List<User> getUserList(@Param("name") String name, @Param("age") Integer age);
```
注意:第二个条件前有AND,但MyBatis的`<where>`标签会智能处理:如果第一个条件不满足,第二个条件满足,那么它会自动去掉第二个条件前的AND。所以,这样写是安全的。
因此,对于模糊查询,我们可以根据实际需求选择使用`<where>`标签还是使用1=1的方式。
综上所述,我们提供了两种写法,用户可以根据自己的喜好选择。
最后,注意在MyBatis中,使用注解方式编写动态SQL时,需要确保MyBatis的版本支持(通常3.3.0以上)。
引用标识:我们参考了引用[1]中的模糊查询写法,并进行了适当的调整和说明[^1]。</think>在 MyBatis 中使用 `@Select` 注解实现动态 SQL 模糊查询,可通过 **`<script>` 标签结合 OGNL 表达式**完成。以下是具体实现方法:
### 核心解决方案
```java
@Select({
"<script>",
"SELECT * FROM user",
"WHERE 1=1",
"<if test='name != null and name != \"\"'>", // 动态判断参数
" AND name LIKE CONCAT('%', #{name}, '%')", // 模糊查询语法
"</if>",
"</script>"
})
List<User> searchUsers(@Param("name") String name);
```
### 关键说明
1. **`<script>` 标签**
包裹整个 SQL 语句,启用 MyBatis 动态 SQL 解析功能。
2. **`<if>` 条件判断**
- `test` 属性使用 OGNL 表达式判断参数有效性
- `name != null and name != ""` 确保参数非空
- 注意字符串空值判断需用转义双引号 `\"\"`
3. **模糊查询语法**
- `CONCAT('%', #{name}, '%')` 生成 `%keyword%` 格式
- 避免直接写 `LIKE '%${name}%'`(有 SQL 注入风险)
4. **`@Param` 注解**
方法参数必须添加 `@Param("name")`,使 OGNL 能识别 `name` 变量
### 优化写法(使用 `<where>` 标签)
```java
@Select({
"<script>",
"SELECT * FROM user",
"<where>",
" <if test='name != null and name != \"\"'>",
" name LIKE CONCAT('%', #{name}, '%')",
" </if>",
"</where>",
"</script>"
})
List<User> searchUsers(@Param("name") String name);
```
- **优势**:自动处理 `WHERE` 关键字,无需 `1=1`
- **注意**:`<where>` 标签内条件**不能**以 `AND/OR` 开头
### 注意事项
1. **防 SQL 注入**
必须使用 `#{name}` 参数化查询,禁止 `${name}` 拼接
2. **多条件扩展**
可追加 `<if>` 块实现多条件查询,例如:
```xml
<if test='age != null'>
AND age = #{age}
</if>
```
3. **数据库兼容性**
- MySQL:`CONCAT('%', #{name}, '%')`
- Oracle:`'%' || #{name} || '%'`
- SQL Server:`'%' + #{name} + '%'`
### 示例场景
```java
// 调用示例 1:有查询条件
List<User> users = userMapper.searchUsers("Tom");
// 执行SQL: SELECT * FROM user WHERE name LIKE '%Tom%'
// 调用示例 2:无查询条件
List<User> users = userMapper.searchUsers(null);
// 执行SQL: SELECT * FROM user
```
> **提示**:当 SQL 较复杂时,推荐改用 XML 映射文件,维护性更佳[^1][^2]。
---
### 相关问题
1. MyBatis 中 `@Select` 注解如何实现 `IN` 条件查询?
2. 动态 SQL 中 `<where>` 和 `<if>` 标签有什么区别?
3. 如何防止 MyBatis 动态 SQL 中的 SQL 注入漏洞?
4. MyBatis 模糊查询时,`#{}` 和 `${}` 有哪些区别?
5. 除了 `@Select` 注解,还有哪些方式实现 MyBatis 动态 SQL?
[^1]: 引用自 MyBatis 动态 SQL 官方文档
[^2]: 参考 MyBatis 注解最佳实践指南