mybatis 用注解实现动态sql与连表查询(provider)

本文介绍MyBatis中使用注解进行SQL映射的方法,包括动态SQL的实现方式,并通过示例展示了如何利用@SelectProvider进行多表联查及条件筛选。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

以前大家在用mybatis的时候比较喜欢用mapper(xml),确实写sql比较方便。但是开发过程中,在xml与代码之间跳来跳去实在太繁琐了,而且添加或者删除字段的成本也比较大。

后面mybatis plus出现了,单表操作确实比较方便,但是遇到多表联查的时候,就比较难受了。

下面看看mybatis官方提供的注解实现方式。

注解

先写一个简单的查询,似乎比较轻松

@Mapper
public interface UserDao {

    @Select("select id, name, age, address from tb_user_info")
    List<UserInfo> listUserInfo();
}

那么,如何实现动态sql呢,仅仅依靠@Select注解是无法实现的,还需要借助mybatis提供的另一个注解@SelectProvider

@Mapper
public interface UserDao {

    @SelectProvider(type = UserInfoProvider.class, method = "listUserInfoByDto")
    List<UserInfo> listUserInfoByDto(@Param("userInfoDto") UserInfoDto userInfoDto);
   
}

创建一个辅助类, 方法名必须和注解中的method属性相对应, 字节码对象需要赋值给注解中的type属性,返回值就是要执行的sql。

public class UserInfoProvider {

    public String listUserInfoByDto(UserInfoDto userInfoDto) {
        String sql = "select id, name, age, address from tb_user_info";
        if(userInfoDto.getAddress() != null) {
            sql += " where address = #{userInfoDto.address}";
        }

        if(userInfoDto.getName() != null) {
            if(userInfoDto.getAddress() != null) {
                sql += " and name = #{userInfoDto.name}";
            } else {
                sql += " where name = #{userInfoDto.name}";
            }
        }
        return sql;
    }

}

SQL辅助类

虽然实现了功能,但是sql拼装太原始,而且一不小心就会出错。mybatis官方提供了一个工具类,我们可以借助他的工具类重新实现这个功能。

    public String listUserInfoByDtoPro(UserInfoDto userInfoDto) {
        return new SQL(){
            {
                SELECT("id, name, age, address");
                FROM("tb_user_info");
                if(userInfoDto.getAddress() != null) {
                    WHERE("address = #{userInfoDto.address}");
                }

                if(userInfoDto.getName() != null) {
                    WHERE("name = #{userInfoDto.name}");
                }
            }
        }.toString();
    }

巧妙地通过了双大括号的写法来初始化SQL工具类,并且屏蔽了sql拼接的过程。(双大括号写法,也是实现匿名类的一种方式,可以参考我的另一篇博客https://blog.csdn.net/qq_37855749/article/details/115913722

多表联查

@Mapper
public interface UserDao {

    @SelectProvider(type = UserInfoProvider.class, method = "listUserWithRole")
    List<UserInfo> listUserWithRole(@Param("userInfoDto") UserInfoDto userInfoDto);

}
public class UserInfoProvider {

    public String listUserWithRole(UserInfoDto userInfoDto) {
        return new SQL(){
            {
                SELECT("tui.id, tui.name, tui.age, tui.address, tr.role_name roleName");
                FROM("tb_user_info tui");
                LEFT_OUTER_JOIN("tb_user_role tur on tui.id = tur.user_id");
                LEFT_OUTER_JOIN("tb_role tr on tur.role_id = tr.id");
                //也可以用下面这种写法
                //LEFT_OUTER_JOIN("tb_user_role tur on tui.id = tur.user_id", "tb_role tr on tur.role_id = tr.id");

                if(userInfoDto.getAddress() != null) {
                    WHERE("address = #{userInfoDto.address}");
                }

                if(userInfoDto.getName() != null) {
                    WHERE("name = #{userInfoDto.name}");
                }
            }
        }.toString();
    }

}

Provider

回过头来看注解,mybatis提供了四种Provider

    @SelectProvider

    @InsertProvider

    @UpdateProvider

    @DeleteProvider

但是我们在编码的过程中发现,其实主要是看UserInfoProvider 这个类中method的返回值,理论上和注解名没有太多关系,那为什么还要区分四个注解呢?

其实是和sql语句执行的返回值有关

    //如果是SelectProvider也可以,但是返回值为null
    @UpdateProvider(type = UserInfoProvider.class, method = "updateUser")
    Integer updateUser(@Param("userInfo") UserInfo userInfo);

更新、删除、修改的返回值都是数值型(数据库中涉及的数据条数),而查询类sql执行的返回值可能是java对象,或者集合之类的,与其他类型不兼容。

比如上述的语句明显是个更新,我如果强行换成DeleteProvider,也能得到正确的结果,因为返回值都是数值类型的。

    @DeleteProvider(type = listUserWithRole.class, method = "updateUser")
    Integer updateUser(@Param("userInfo") UserInfo userInfo);

但肯定是不推荐的,语义化的注解让开发人员一看就知道当前的sql是什么类型的,直观且具体。

总结

Provider需要的是原始的sql,我们一般会定义辅助类提供所需要的sql。

不一定要用官方的SQL拼装工具类,比如我们公司有人就是自己造了个轮子。

但我觉得SQL工具类还是很好用的,并且看了看里面的源码,明天再写一篇简单的源码分析。(已写:https://blog.csdn.net/qq_37855749/article/details/115932800

另外要注意,Provider工具类(例子中的UserInfoProvider)可以有很多类,不一定要放在一个文件里。但是方法名不能相同,不要用重载,mybatis是根据方法名定位的。

资源

如果需要测试的源码:

https://gitee.com/DayCloud/dayrain-demo/tree/master/mybatis-provider

### 使用 MyBatis 注解编写动态 SQL #### 动态 SQL 的基本概念 MyBatis 提供了多种注解用于简化 SQL 操作的定义和执行过程,提高开发效率和代码可读性[^1]。对于复杂的 SQL 查询需求,可以通过 `@SelectProvider`、`@UpdateProvider`、`@InsertProvider` 和 `@DeleteProvider` 这些注解,在接口方法上指定一个 Java 方法来动态构建 SQL 语句。 #### 编写动态 SQL 示例 假设有一个名为 `User` 的实体类以及对应的数据库 `users`,下面展示如何利用上述提到的 Provider 方式创建动态查询: ```java // 定义提供者类 public class UserSqlProvider { /** * 构建基于条件的选择查询 */ public String selectUsers(Map<String, Object> params) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("SELECT * FROM users WHERE 1=1"); if (params.containsKey("name")) { sqlBuilder.append(" AND name LIKE CONCAT('%', #{name}, '%')"); } if (params.containsKey("ageMin") && params.containsKey("ageMax")) { sqlBuilder.append(" AND age BETWEEN #{ageMin} AND #{ageMax}"); } return sqlBuilder.toString(); } } ``` 接着是在 Mapper 接口中应用这个提供者的例子: ```java import org.apache.ibatis.annotations.SelectProvider; import java.util.List; public interface UserMapper { @SelectProvider(type=UserSqlProvider.class, method="selectUsers") List<User> findUsersByConditions(@Param("params") Map<String,Object> conditions); } ``` 在这个例子中,`findUsersByConditions()` 方法会接收一个参数映射作为输入,并将其传递给 `UserSqlProvider.selectUsers()` 来生成最终要执行的 SQL 字符串[^2]。 #### 实际值处理流程 当调用 `findUsersByConditions()` 并传入具体的参数时,这些参数会被注入到 `Map<String, Object>` 中并传递至 `selectUsers()` 函数内部。函数内根据哪些字段有值来进行相应的逻辑判断,从而拼接出完整的 SQL 达式[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值