技术框架中对MyBatis的一对一查询的学习

高级查询之一对一查询

查询条件

根据游戏角色ID,查询账号信息

我们在之前创建的映射器接口 GameMapper.java 中添加接口方法,如下:

复制代码
	/**
     * 根据角色ID查询账号信息
     * @param id 角色Id
     * @return 角色实体对象
     */
    public RoleEntity selectRoleById(int id);

接下来,我分别演示关联查询和子查询方式实现接口方法的映射。

关联查询

方式一

现在我们暂时先抛开 MyBatis 框架,直接从数据库出发写一写关联查询的 SQL 语句,如下:

复制代码
select r.*,a.* from tb_role as r join tb_account as a on r.account_id=a.id where r.id=1

另外,还有一种不使用 join 关键字的 SQL 语句写法,如下:

复制代码
select r.*,a.* from tb_role as r,tb_account as a where r.account_id=a.id and r.id=1

第一种写法,使用 join 关键字,本质上是采用的内连接(inner join)。

第二种写法,不使用 join 关键字,本质上是采用交叉连接(cross join),也即生成两表的笛卡尔积,得到的记录相当于两表记录的乘积。

以上两种写法查询结果是相同的,但推荐使用第一种,因为第一种性能比第二种高,特别是在有大表的情况下。

理由是第二种交叉连接将产生更多的记录,然后通过 where 后的 r.account_id=a.id 条件过滤不需要的记录,而第一种内连接会直接过滤不需要的记录,所以执行效率更高。

我们执行一下查询结果,如下:

现在,我们回到 MyBatis 框架,看一下在 XML 映射文件中,如何实现关联查询映射。

首先,我们需要建立 RoleEntity 实体类与 AccountEntity 实体类之间的关联关系,如下:

复制代码
public class RoleEntity {
    private int id;
    private String profession;
    private int rank;
    private int money;
    private AccountEntity account; //关联引用属性
    ...
}

这样,通过账号名查询的账号信息就可以映射到 account 属性中。注意 account 属性的 get 和 set 方法要记得添加上去,还有 toString 方法要重写一下,添加 account 属性的打印信息。

现在,我们来编写映射文件中 SQL 语句映射,如下:

复制代码
<?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="mapper.GameMapper">
    <resultMap id="roleResultMap" type="entity.RoleEntity">
        <id property="id" column="id" />
        <result property="profession" column="profession" />
        <result property="rank" column="rank" />
        <result property="money" column="money" />
        <result property="account.id" column="aid" />
        <result property="account.username" column="username" />
        <result property="account.password" column="password" />
    </resultMap>

    <select id="selectRoleById" resultMap="roleResultMap">
        select r.*,a.id as aid,a.user_name,a.password from tb_role as r join tb_account as a on r.account_id=a.id where r.id=#{id}
    </select>
</mapper>

这个地方使用到了级联赋值,多级之间用.进行引用,此处我们只有一级,可以有很多级。

注意事项:

select r.*,a.*from tb_role as r join tb_account as a on r.account_id=a.id where r.id=#{id} 写法存在的问题:

这会存在两个相同字段名 id,结果集映射时 accout 的 id 值会被 role 的 id 值所覆盖

方式二

和之前单表映射相比,没什么太大差别,就是多了 association 子标记以及相应的内容罢了。

意思很也简单,就是将关联查询结果中的账号信息,具体而言就是 a.id,a.user_name,a.password ,映射到 AccountEntity 实例中,也就是 RoleEntity.account 属性。

现在,我们来编写映射文件中 SQL 语句映射,如下:

复制代码
<?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="mapper.GameMapper">
    <resultMap id="roleResultMap" type="entity.RoleEntity">
        <id property="id" column="id" />
        <result property="profession" column="profession" />
        <result property="rank" column="rank" />
        <result property="money" column="money" />
        <!-- association:用于映射关联查询单个对象信息
		         property:将关联查询的账号信息映射到属性 account 上 -->
        <association property="account" javaType="entity.AccountEntity">
            <id property="id" column="aid" />
            <result property="userName" column="user_name" />
            <result property="password" column="password" />
        </association>
    </resultMap>

    <select id="selectRoleById" resultMap="roleResultMap">
        select r.*,a.id as aid,a.user_name,a.password from tb_role as r join tb_account as a on r.account_id=a.id where r.id=#{id}
    </select>
</mapper>

autoMapping 属性:自动映射开关,关联查询默认 false,当设置为 true 则当字段名和实体类属性名相同时可以自动映射

最后,我们在 MyBatisTest 中添加一个单元测试方法,如下:

复制代码
@Test
public void selectRoleByAccountNameTest() {
    RoleEntity roleEntity = gameMapper.selectRoleById(1);
    System.out.println(roleEntity);

    Assert.assertNotNull(roleEntity);
}

执行测试,结果如下:

复制代码
2020-07-15 10:49:28,860 [main] [mapper.GameMapper.selectRoleById]-[DEBUG] ==>  Preparing: select r.*,a.id,a.user_name,a.password from tb_role as r join tb_account as a on r.account_id=a.id where r.id=? 
2020-07-15 10:49:28,954 [main] [mapper.GameMapper.selectRoleById]-[DEBUG] ==> Parameters: 1(Integer)
2020-07-15 10:49:28,985 [main] [mapper.GameMapper.selectRoleById]-[DEBUG] <==      Total: 1
RoleEntity{id=1, profession='战士', rank=10, money=2000, account=AccountEntity{id=1, userName='潇洒哥', password='12345'}}

这样,我们通过游戏角色ID查询到了游戏账号信息,并封装在 RoleEntity 对象的 account 属性中。

子查询

方式一

老规矩,现在我们暂时先抛开 MyBatis 框架,直接从数据库出发写一写子查询的 SQL 语句,如下:

复制代码
select * from tb_account where id=(select account_id from tb_role where id=1)

这条子查询 SQL 语句由两条 select 语句组成,不用过多解释了,应该很容易理解。

现在,我们回到 MyBatis 框架,之前关联查询时我们已经建立 RoleEntity 实体类与 AccountEntity 实体类之间的关联关系,所以,这里我们直接来编写映射文件中 SQL 语句映射,如下:

复制代码
<?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="mapper.GameMapper">
    <resultMap id="roleResultMap" type="entity.RoleEntity">
        <id property="id" column="id" />
        <result property="profession" column="profession" />
        <result property="rank" column="rank" />
        <result property="money" column="money" />
      
        <!-- association:用于映射关联查询单个对象信息
		         property:将关联查询的账号信息映射到属性 account 上
 						 column:传递子查询参数
						 select:子查询id
				-->
        <association property="account" javaType="entity.AccountEntity"
                     column="account_id" select="selectAccountById">
        </association>
    </resultMap>

    <select id="selectRoleById" resultMap="roleResultMap">
        select * from tb_role where id=#{id}
    </select>
    <select id="selectAccountById" resultType="entity.AccountEntity">
        select * from tb_account where id=#{accountId}
    </select>
</mapper>

以上可以看到,子查询和关联查询不一样的地方在于 association 标记多了 column 和 select 这两个属性。

  • column 的值是 account_id,这是 tb_role 表的外键 ID 字段名,意思是将该字段名对应的字段值作为参数传递给指定的子查询
  • select 的值是 selectAccountById,这是子查询 select 语句的ID,表示指定 ID 定义的子查询,也就是 select * from tb_account where id=#{accountId} 这条查询语句。这里的参数 #{accountId} 对应的就是column 的值

以上子查询的参数只有一个,那么如果子查询的参数不止一个,又该怎么办呢?比如是复合主键。如果要处理复合主键,可以使用column= "{prop1=col1,prop2=col2}" ,prop表示类属性名,col表示表字段名。

执行测试,结果如下:

复制代码
2020-07-15 10:57:56,314 [main] [mapper.GameMapper.selectRoleById]-[DEBUG] ==>  Preparing: select * from tb_role where id=? 
2020-07-15 10:57:56,360 [main] [mapper.GameMapper.selectRoleById]-[DEBUG] ==> Parameters: 1(Integer)
2020-07-15 10:57:56,392 [main] [mapper.GameMapper.selectAccountById]-[DEBUG] ====>  Preparing: select * from tb_account where id=? 
2020-07-15 10:57:56,392 [main] [mapper.GameMapper.selectAccountById]-[DEBUG] ====> Parameters: 1(Integer)
2020-07-15 10:57:56,392 [main] [mapper.GameMapper.selectAccountById]-[DEBUG] <====      Total: 1
2020-07-15 10:57:56,392 [main] [mapper.GameMapper.selectRoleById]-[DEBUG] <==      Total: 1
RoleEntity{id=1, profession='战士', rank=10, money=2000, account=AccountEntity{id=1, userName='null', password='12345'}}

结果和关联查询一样,而且从打印调试信息可以看到,子查询是先后执行了两条简单的 select 语句。

这里还有一个小问题,就是账号信息的 userName 属性值是 null。为何会如此呀,原因是 tb_account 字段名是user_name,AccountEntity 实体类属性名是 userName,无法使用 resultType 自动映射。

解决办法有两个:

  • 子查询结果集映射不使用 resultType 自动映射,改为 resultMap 手动映射,如下:

    复制代码
    <resultMap id="accountResultMap" type="entity.AccountEntity">
       <id property="id" column="id" />
       <result property="userName" column="user_name" />
       <result property="password" column="password" />
    </resultMap>
    <select id="selectAccountById" resultMap="accountResultMap">
            select * from tb_account where id=#{accountId}
    </select>
    
  • 在全局配置文件 mybatis-config.xml 开启 settings 开关

    复制代码
    <configuration>  
      <settings>
         <!-- 开启自动驼峰命名规则映射开关 -->     
         <setting name="mapUnderscoreToCamelCase" value="true"/>
      </settings>        
      ...
    </configuration>
    

结果如下:

复制代码
RoleEntity{id=1, profession='战士', rank=10, money=2000, account=AccountEntity{id=1, userName='潇洒哥', password='12345'}}

userName 已经有值了,说明问题得以解决。

方式二

子查询方式一种给第二个查询传递了一个参数,如果需要给第二个查询传递多个参数怎么办呢?

复制代码
<association property="属性" select="查询对应的select的id" 
             column="{key1=父查询字段1,key2=父查询字段2,key3=父查询字段3}" />

这种相当于给子查询传递了一个 map,子查询中 需要用过 map 的 key 获取对应的条件

复制代码
<resultMap id="roleResultMap" type="entity.RoleEntity">
    <id property="id" column="id" />
    <result property="profession" column="profession" />
    <result property="rank" column="rank" />
    <result property="money" column="money" />

    <!-- association:用于映射关联查询单个对象信息
           property:将关联查询的账号信息映射到属性 account 上
        column:传递子查询参数
       select:子查询id
    -->
    <association property="account" javaType="entity.AccountEntity"
                 column="{aid=account_id,rid=id}" select="selectAccountById">
    </association>
</resultMap>
posted @   BingBing爱化学-04044  阅读(17)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示