mybatis - 关于多表查询 结果的两种封装方式(注解版 , xml版)

本文详细介绍了在MyBatis中实现一对多查询的方法,包括XML配置文件和注解版的使用,通过具体案例展示了如何查询用户及其关联的账号集合。

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

需求: 一对多查询,查询用户,同时查询账号的集合!

案例分析:
       1.一个用户可以有多个账号 , 也可以没有账号.
       2.所以sql语句使用左表查询 ( 以左表为主 )
在这里插入图片描述
一、配置文件XML的使用!
准备数据:
       1.创建数据库 并 初始化数据 !
       2.创建表对应的JavaBean 类.

User类:

public class User implements Serializable {
    private int id;
    private String username;
    private String sex;
    private String address;
    private Date birthday;

    // 用户表中添加账号集合.
    private List<Account> accountList;
    
	// 提供get / set 方法  和 toString方法. 
}

Account类:

public class Account {
    private int id;
    private int uid;
    private double money;
	
	// 提供get / set 方法  和 toString方法. 
}

UserMapper接口:

public interface UserMapper {
    //  一对多查询:  查询用户,同时查询账号的集合
    public List<User> findUserAndAccountList();
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--  接口的全路径名  -->
<mapper namespace="com.aaa.dao.UserMapper">
	<!--  一对多查询,  查询用户,同时查询账号的集合  -->
    <resultMap id="aaa" type="User">
        <!-- 主键字段 -->
        <id property="id" column="user_id"></id>
        <result property="username" column="username"></result>
        <result property="sex" column="sex"></result>
        <result property="address" column="address"></result>
        <result property="birthday" column="birthday"></result>
        <!-- 关联账号的集合属性 collectoin -->
        <collection property="accountList" column="account_id" ofType="Account">
            <id property="id" column="account_id"></id>
            <result property="uid" column="uid"></result>
            <result property="money" column="money"></result>
        </collection>
    </resultMap>
    <select id="findUserAndAccountList" resultMap="aaa">
        select u.*,a.*,a.UID account_id,u.id user_id
        from user u left join account a
        on u.id = a.uid
    </select> 
</mapper>

主配置文件: mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 引入jdbc的配置文件.  -->
    <properties resource="jdbc.properties"></properties>

    <!-- 设置 -> 返回类型的别名 -->
    <typeAliases>
        <!-- 扫描包:这个包下的所有类都以类名为别名   -->
        <package name="com.aaa.domain"></package>
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!-- 扫描包:
               前提: 接口的包名必须要和关联sql语句的配置文件包名一致.
         -->
        <package name="com.aaa.dao"></package>
    </mappers>
</configuration>

测试类:

public class UserMapperTest {
    InputStream in = null;
    SqlSession sqlSession= null;
    UserMapper mapper = null;

    @Before
    public void init() throws Exception {
        // 1.读取主配置文件的输入流.
        in = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
        // 2.获取sql对象.
        sqlSession = sqlSessionFactory.openSession();
        // 3.获取dao接口的实现类对象(代理)
        mapper = sqlSession.getMapper(UserMapper.class);
    }

    @After
    public void destory() throws Exception {
        // 提交事务.
        sqlSession.commit();

        // 5.释放资源.
        sqlSession.close();
        in.close();
    }
   
    @Test
    public void findUserAndAccountAndRoleList() {
        User user = mapper.findUserAndAccountAndRoleList(41);
        System.out.println(user);
    }
}

 
二、注解版的使用.

注: 使用注解版 , 就不能使用xml配置文件了.

UserMapper.java

public interface UserDao {
    /**
     *  一对多查询:
     *     查询用户信息 并且 查询账号集合信息.
     */
    @Select("select * from user ")
    @Results(id = "findUserAndAccontResult" , value = {
            @Result(id = true , property = "userId" , column = "id"),
            @Result(property = "username" , column = "username"),
            @Result(property = "userBirthday" , column = "birthday"),
            @Result(property = "userSex" , column = "sex"),
            @Result(property = "userAddress" , column = "address"),
            // 封装账户集合信息.
            @Result(property = "accountList" , column = "id" ,
                    many = @Many(select = "com.aaa.dao.AccountDao.findByUserId" , fetchType = FetchType.LAZY))
    })
    public List<User> findUserAndAccount();

AccountDao.java

public interface AccountDao {
	// 根据uid查询账号信息.
    @Select("SELECT * FROM account WHERE uid=#{user_id}")
    public List<Account> findByUserId(@Param("user_id") int user_id);
}
<think>我们正在讨论的是MyBatis Plus中LambdaQueryWrapper在查询时如何使用其他的字段作为查询条件。在单查询中,LambdaQueryWrapper非常方便,因为它可以通过方法引用来避免硬编码字段名。但在查询时,我们需要考虑如何关联其他并引用其他的字段。 根据引用[1],LambdaQueryWrapper和QueryWrapper都是MyBatis Plus的查询条件封装类,其中LambdaQueryWrapper使用Lambda达式,而QueryWrapper使用字符串。LambdaQueryWrapper的优点在于编译时类型安全和避免字段名拼写错误,但在查询中,它默认只能处理当前实体对应的。 那么,如何实现查询条件呢?我们可以通过以下两种主要方式: 1. **使用JOIN语句(手动编写SQL)并配合条件构造器**:这种方式下,我们可以使用QueryWrapper或LambdaQueryWrapper来构建条件,但需要自己编写JOIN语句,并且为其他的字段设置别名,然后在条件中使用这些别名。 2. **使用MyBatis Plus的联查询功能(如@TableField注解的one和many属性)**:这种方式适用于定义好的实体关系,可以通过关联关系自动查询。但是,这种方式在条件构造上可能不够灵活。 然而,LambdaQueryWrapper本身并不直接支持跨条件,所以我们需要结合其他方式。下面介绍两种常见方法: ### 方法一:使用QueryWrapper并手动指定别名(字符串方式) 我们可以使用QueryWrapper,通过字符串指定别名和字段名: ```java QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.select("u.*, o.order_no") .from("user u") .leftJoin("order o ON u.id = o.user_id") .eq("o.status", 1); // 这里使用订单的status字段作为条件 ``` 但是这种方式不是类型安全的,且容易出错。 ### 方法二:使用LambdaQueryWrapper结合自定义SQL(推荐) 我们可以利用LambdaQueryWrapper来构建条件,然后通过自定义SQL语句实现关联。具体步骤: 1. 在Mapper接口中定义自定义查询方法,使用@Select注解XML映射文件编写SQL。 2. 在SQL中,我们使用JOIN关联,并为同名字段设置别名以避免冲突。 3. 在条件部分,我们可以使用LambdaQueryWrapper来构建条件,但注意,LambdaQueryWrapper只能构建当前实体对应的条件。对于其他,我们需要在SQL中显式写出条件,或者将条件值传入到自定义方法中,然后在SQL中使用。 然而,LambdaQueryWrapper并不能直接用于条件。因此,我们可以这样做: 在自定义SQL中,我们使用参数传递条件值,然后在SQL中写条件。 但是,如果我们希望构建一个统一的条件构造器,我们可以使用MyBatis Plus的`join`方法(在3.4.0本以上支持)?实际上,官方文档中并没有提供LambdaQueryWrapper直接跨的功能。所以,我们可能需要使用字符串方式,或者采用以下方式: ### 方法三:使用MyBatis Plus的JoinLambdaWrapper(第三方扩展) 有一个第三方库`mybatis-plus-join`(https://github.com/yulichang/mybatis-plus-join)提供了对查询的支持,它扩展了LambdaQueryWrapper,支持条件。 示例: ```java JoinLambdaWrapper<User> wrapper = new JoinLambdaWrapper<>(User.class) .selectAll(User.class) .select(Order::getOrderNo) .leftJoin(Order.class, Order::getUserId, User::getId) .eq(Order::getStatus, 1); List<User> userList = userMapper.selectJoinList(User.class, wrapper); ``` 这种方式可以让我们使用Lambda达式来引用其他的字段。 但是,如果你不想引入第三方库,那么可能只能选择方法一或方法二。 鉴于MyBatis Plus本身并没有提供Lambda的条件构造,我们通常采用以下两种方式之一: - **方式A(纯手动SQL)**:在Mapper接口中定义方法,使用@Select注解XML编写查询SQL,然后通过参数传递条件值。 - **方式B(QueryWrapper+字符串)**:使用QueryWrapper的字符串方式指定条件。 ### 总结 在MyBatis Plus中,使用LambdaQueryWrapper直接使用其他的字段作为条件是不直接支持的。因此,我们通常需要: 1. 使用自定义SQL(XML注解)完成查询,在SQL中处理关联和字段别名。 2. 如果需要动态条件,可以在自定义SQL中使用MyBatis的动态SQL标签(如<if>)或者将QueryWrapper作为参数传入,然后使用`${ew.customSqlSegment}`来插入条件(注意SQL注入风险,但MyBatis Plus做了处理,通常安全)。 例如,在Mapper接口中: ```java @Select("SELECT u.*, o.order_no FROM user u LEFT JOIN order o ON u.id=o.user_id ${ew.customSqlSegment}") List<User> selectUserOrderList(@Param(Constants.WRAPPER) QueryWrapper<User> wrapper); ``` 然后,我们可以构建一个QueryWrapper,并添加条件: ```java QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.eq("o.status", 1); List<User> list = userMapper.selectUserOrderList(wrapper); ``` 但是,这里我们使用的是字符串形式的字段名,而且需要知道别名(如"o.status")。 如果我们想用Lambda方式,目前MyBatis Plus原生不支持。因此,如果你坚持要用Lambda达式,可以考虑引入第三方库`mybatis-plus-join`。 最后,根据引用[4],LambdaQueryWrapper是通过Lambda达式的序列化机制来获取字段名的,这要求达式必须是实体类的属性。因此,跨的情况下,它无法直接获取其他实体类的字段名。 所以,针对你的问题,答案如下: - 原生MyBatis Plus不支持LambdaQueryWrapper跨条件,推荐使用自定义SQL+QueryWrapper(字符串方式)或引入第三方库。 如果你使用的是MyBatis Plus 3.4.0及以上本,官方提供了一种新的方式:使用`selectMaps`或自定义ResultMap,但仍然没有提供Lambda跨条件。 因此,建议根据项目情况选择方案: 1. 如果条件简单,使用字符串方式。 2. 如果条件复杂且需要类型安全,引入第三方库`mybatis-plus-join`。 3. 或者,使用自定义SQL(XML)编写查询,这样最灵活。 下面是一个使用第三方库的示例(假设已引入依赖): ```java // 使用 mybatis-plus-join LambdaJoinWrapper<User> wrapper = JoinWrappers.lambda(User.class) .selectAll(User.class) .selectAs(Order::getOrderNo, UserDTO::getOrderNo) // 设置别名到DTO .leftJoin(Order.class, Order::getUserId, User::getId) .eq(Order::getStatus, 1); // 使用Order的字段作为条件 List<UserDTO> list = userMapper.joinSelectList(wrapper, UserDTO.class); ``` 这样,我们就可以用Lambda达式引用其他的字段了。 希望这能解决你的问题。</think>在 MyBatis Plus 中,使用 `LambdaQueryWrapper` 进行查询并引用其他的字段作为条件,需要通过 **JOIN 操作** 和 **别名** 来实现。以下是具体解决方案: --- ### 解决方案:JOIN 操作 + 别名 #### 步骤 1:定义关联查询(Mapper 层) ```java @Select("SELECT u.*, o.order_no " + "FROM user u " + "LEFT JOIN order o ON u.id = o.user_id " + "${ew.customSqlSegment}") // 动态插入条件 IPage<UserOrderDTO> selectUserOrders(Page<?> page, @Param(Constants.WRAPPER) LambdaQueryWrapper<User> wrapper); ``` #### 步骤 2:构建跨条件(Service 层) ```java public IPage<UserOrderDTO> searchOrders(String productName) { LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); // 使用别名引用其他字段 wrapper.apply("o.product_name = {0}", productName) // ✅ 正确:order字段 .eq(User::getStatus, 1); // ✅ 正确:user字段 return userMapper.selectUserOrders(new Page<>(1, 10), wrapper); } ``` #### 关键点说明: 1. **别名机制**: - 在 SQL 中通过 `o.product_name` 明确指定 `order` 字段 - 避免直接写 `product_name`(可能引起歧义) 2. **动态条件注入**: - `${ew.customSqlSegment}` 自动插入 `LambdaQueryWrapper` 生成的条件 - 安全提示:确保 `apply()` 参数使用预编译占位符 `{0}` 防止 SQL 注入 3. **混合条件支持**: ```java // 同时使用本(Lambda) + 他(别名)条件 wrapper.apply("o.price > {0}", 100) .ge(User::getAge, 18); ``` --- ### 其他字段的查询方式对比 | 场景 | 正确方式 | 错误方式 | |-------------------------------|----------------------------------|------------------------| | 引用当前字段 | `eq(User::getName, "Tom")` | 无 | | 引用关联字段(WHERE 条件) | `apply("o.product_name={0}",x)` | `eq(Order::getName,x)`[^1] | | 引用关联字段(SELECT 字段)| SQL 中写 `o.column AS alias` | 实体类直接添加字段[^2]| > **原理说明**: > `LambdaQueryWrapper` 基于当前实体类(如 `User`)的字段生成 SQL,无法直接识别其他实体(如 `Order`)的字段[^4]。必须通过 **显式别名** 指定关联字段。 --- ### 复杂场景:动态条件 ```java wrapper // 关联条件 .apply("o.create_time BETWEEN {0} AND {1}", startTime, endTime) // 本条件 .in(User::getDeptId, deptIds) // 嵌套条件 .and(w -> w.apply("o.status={0}", 1).or().gt(User::getScore, 90)); ``` --- ### 最佳实践建议 1. **始终使用别名**:查询时为每个字段显式指定 `table_alias.column` 2. **DTO 接收结果**:避免污染实体类,用 DTO 接收跨查询结果 3. **参数化传递**:`apply()` 中使用 `{0}`/`{1}` 占位符确保安全 4. **XML 管理复杂 SQL**:超过 3 关联时建议使用 XML 映射文件 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值