MyBatisPlus自定义SQL怎么写?三种方式实现复杂查询与分页
作者:csdn2015_
还在为MyBatisPlus复杂查询发愁吗?本文手把手教你三种自定义SQL写法,包括注解、XML和Wrapper条件构造器,轻松搞定多表关联、动态条件和聚合统计,同时讲解分页查询配置,避免分页失效,并提供防SQL注入和性能优化技巧,让你的数据访问层更高效、更安全
在 MyBatis Plus 中,虽然提供了丰富的内置方法,但在面对复杂查询(如多表关联、自定义条件、聚合统计等)时,仍需编写自定义 SQL。
以下是完整的自定义查询 SQL 编写指南,涵盖主流方式、分页支持、多表查询及最佳实践。
一、自定义 SQL 的三种主流方式
1. 注解方式(@Select)
适用于简单查询,直接在 Mapper 接口中编写 SQL。
@Mapper
public interface UserMapper extends BaseMapper<User> {
@Select("SELECT * FROM user WHERE age > #{age}")
List<User> selectByAge(@Param("age") Integer age);
@Select("SELECT u.*, o.order_no FROM user u " +
"LEFT JOIN orders o ON u.id = o.user_id " +
"WHERE u.status = #{status}")
List<UserOrderVO> selectUserWithOrders(@Param("status") Integer status);
}2. XML 方式(推荐用于复杂 SQL)
在 resources/mapper/UserMapper.xml 中定义 SQL:
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectByCondition" resultType="com.example.vo.UserVO">
SELECT u.*, o.order_no
FROM user u
LEFT JOIN orders o ON u.id = o.user_id
<where>
<if test="age != null">
AND u.age > #{age}
</if>
<if test="status != null">
AND u.status = #{status}
</if>
</where>
ORDER BY u.create_time DESC
</select>
</mapper>Mapper 接口定义:
List<UserVO> selectByCondition(@Param("age") Integer age, @Param("status") Integer status);3. 使用 Wrapper 条件构造器(适合动态条件)
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("status", 1)
.gt("age", 18)
.orderByDesc("create_time");
List<User> users = userMapper.selectList(wrapper);支持嵌套条件、LIKE、IN、排序等:
wrapper.and(w -> w.eq("name", "张三").or().eq("name", "李四"))
.like("email", "@gmail.com")
.in("status", 1, 2, 3);二、分页查询支持
1. 注解 + 分页
@Select("SELECT * FROM user WHERE status = #{status}")
IPage<User> selectByStatusPage(IPage<User> page, @Param("status") Integer status);2. XML + 分页
<select id="selectUserPage" resultType="com.example.vo.UserVO">
SELECT u.*, o.order_no
FROM user u
LEFT JOIN orders o ON u.id = o.user_id
<where>
<if test="ew.paramNameValuePairs.status != null">
AND u.status = #{ew.paramNameValuePairs.status}
</if>
</where>
</select>Java 调用:
IPage<UserVO> page = userMapper.selectUserPage(
new Page<>(1, 10),
Wrappers.<User>lambdaQuery().eq(User::getStatus, 1)
);注意:分页需配置 PaginationInnerInterceptor,否则分页无效。
三、多表关联查询
1. 定义 VO 接收结果
@Data
public class UserOrderVO {
private Long userId;
private String userName;
private String orderNo;
private BigDecimal amount;
}2. Mapper 接口
@Select("SELECT u.id AS userId, u.name AS userName, o.order_no, o.amount " +
"FROM user u JOIN orders o ON u.id = o.user_id " +
"WHERE u.id = #{userId}")
List<UserOrderVO> selectUserOrders(@Param("userId") Long userId);3. XML 方式(推荐)
<select id="selectUserOrderDetail" resultType="com.example.vo.UserOrderDetailVO">
SELECT
u.id AS userId, u.name AS userName,
o.id AS orderId, o.order_no, o.amount,
oi.product_name, oi.quantity
FROM user u
JOIN orders o ON u.id = o.user_id
JOIN order_item oi ON o.id = oi.order_id
WHERE u.id = #{userId}
</select>四、动态 SQL 示例(XML)
<select id="selectDynamic" resultType="User">
SELECT * FROM user
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="age != null">
AND age >= #{age}
</if>
<if test="statusList != null and statusList.size() > 0">
AND status IN
<foreach item="s" collection="statusList" open="(" separator="," close=")">
#{s}
</foreach>
</if>
<if test="startTime != null">
AND create_time >= #{startTime}
</if>
<if test="endTime != null">
AND create_time <= #{endTime}
</if>
</where>
ORDER BY create_time DESC
</select>五、自定义 ResultMap(字段映射)
<resultMap id="UserOrderMap" type="com.example.vo.UserOrderVO">
<id property="userId" column="user_id"/>
<result property="userName" column="user_name"/>
<result property="orderNo" column="order_no"/>
<result property="amount" column="amount"/>
</resultMap>
<select id="selectUserOrder" resultMap="UserOrderMap">
SELECT u.id AS user_id, u.name AS user_name, o.order_no, o.amount
FROM user u JOIN orders o ON u.id = o.user_id
WHERE u.id = #{userId}
</select>六、最佳实践与注意事项
| 项目 | 建议 |
|---|---|
| SQL 编写方式 | 简单查询用注解,复杂查询用 XML |
| 参数传递 | 多参数使用 @Param 注解 |
| 字段映射 | 使用 ResultMap 明确字段对应关系 |
| 分页支持 | 配置 PaginationInnerInterceptor,使用 IPage |
| 防 SQL 注入 | 使用 #{} 而非 ${} |
| 性能优化 | 避免 SELECT *,按需查询字段 |
| 可维护性 | 将复杂 SQL 放入 XML,便于管理 |
| 条件构造器 | 动态查询优先使用 QueryWrapper / LambdaQueryWrapper |
七、分页拦截器配置(Spring Boot)
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}通过上述方式,
你可以灵活地在 MyBatis Plus 中编写自定义 SQL,满足从简单到复杂的各种查询需求,同时保持代码的可维护性和性能优化。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
