MyBatis关联查询

持久层关联查询是一个比较复杂的问题,每个框架的学习笔记中都会专门列出一篇进行说明,不过MyBatis作为一个半自动的ORM框架,它的关联查询功能则比较简陋,MyBatis只负责映射,JOIN或子查询SQL语句还是我们自己编写的,此外MyBatis还提供了嵌套查询功能。这里以例子的方式,说明MyBatis如何实现各种关联查询。

环境准备

这里创建两张表,t_user用户表和t_role角色表具有一对一关系。

CREATE TABLE t_role (
    role_id BIGINT AUTO_INCREMENT,
    rolename VARCHAR(255) NOT NULL,
    PRIMARY KEY (role_id)
);
CREATE TABLE t_user (
    user_id BIGINT AUTO_INCREMENT,
    username VARCHAR(255) NOT NULL,
    email VARCHAR(255) NOT NULL,
    password VARCHAR(255) NOT NULL,
    role_id BIGINT,
    PRIMARY KEY (user_id),
    FOREIGN KEY (role_id)
        REFERENCES t_role (role_id)
        ON DELETE SET NULL ON UPDATE CASCADE
);

实体类定义如下面代码所示,User类中持有Role类的引用。

User.java

@Data
public class User {
    private Long userId;
    private String username;
    private String email;
    private String password;
    private Role role;
}

Role.java

@Data
public class Role {
    private Long roleId;
    private String rolename;
}

一对一连接查询

一对一连接查询

下面例子中,使用自动映射进行一对一连接查询。

UserMapper.java

public interface UserMapper {
    User selectUserById(Long id);
}

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.gacfox.demo.mapper.UserMapper">
    <select id="selectUserById" resultType="com.gacfox.demo.model.User">
        SELECT u.user_id,
               u.username,
               u.email,
               u.password,
               r.role_id  AS "role.roleId",
               r.rolename AS "role.rolename"
        FROM t_user u
                 INNER JOIN t_role r ON u.role_id = r.role_id
        WHERE u.user_id = #{id}
    </select>
</mapper>

注意XML映射配置的写法,这里使用了内连接SQL语句,将用户表和角色表的所有字段按照一对一的关系全部查了出来,然后映射到用户对象和用户类内的角色属性对象,其中要想成功将user.role属性进行映射,SQL中需要为角色表的字段起别名为role.x,别名点前的名字和User中的属性名对应,除此之外,因为别名中包含了点.,所以需要用双引号括起来。

一对一连接查询(使用resultMap映射)

除了可以使用resultType自动映射外,我们还可以使用resultMap配置进行手动映射,下面是使用手动映射对一对一关联查询进行配置的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.gacfox.demo.mapper.UserMapper">
    <resultMap id="userMap" type="com.gacfox.demo.model.User">
        <id property="userId" column="user_id"/>
        <result property="username" column="username"/>
        <result property="email" column="email"/>
        <result property="password" column="password"/>
        <result property="role.roleId" column="role_id"/>
        <result property="role.rolename" column="rolename"/>
    </resultMap>
    <select id="selectUserById" resultMap="userMap">
        SELECT u.user_id,
               u.username,
               u.email,
               u.password,
               r.role_id,
               r.rolename
        FROM t_user u
                 INNER JOIN t_role r ON u.role_id = r.role_id
        WHERE u.user_id = #{id}
    </select>
</mapper>

由于使用手动映射配置,这里我们就不需要在SQL里写别名了,注意手动映射的几个<result>配置,其中用户类的role属性对应的property也是role.x,别名点前的名字和User中的属性名对应。

一对一嵌套查询

除了连接查询,我们还经常使用嵌套查询实现关联查询,嵌套查询能够实现懒加载,在性能上和连接查询各有优缺点。MyBatis中,我们可以使用<association>标签进行一对一嵌套关联。为了实现嵌套查询,我们还必须在RoleMapper中定义一个selectRoleById()方法用于嵌套查询调用。

UserMapper.java

public interface UserMapper {
    User selectUserById(Long id);
}

RoleMapper.java

public interface RoleMapper {
    Role selectRoleById(Long roleId);
}

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.gacfox.demo.mapper.UserMapper">
    <resultMap id="userMap" type="com.gacfox.demo.model.User">
        <id property="userId" column="user_id"/>
        <result property="username" column="username"/>
        <result property="email" column="email"/>
        <result property="password" column="password"/>
        <association property="role" column="{roleId=role_id}"
                     select="com.gacfox.demo.mapper.RoleMapper.selectRoleById"/>
    </resultMap>
    <select id="selectUserById" resultMap="userMap">
        SELECT user_id, username, email, password, role_id
        FROM t_user
        WHERE user_id = #{id}
    </select>
</mapper>

关于<association>标签,说明以下几点属性:

  • column:作为嵌套查询参数的字段,上面例子中,roleId是嵌套查询的参数名,role_id是数据库中外键的字段名。
  • select:嵌套调用哪个查询方法。

RoleMapper.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.gacfox.demo.mapper.RoleMapper">
    <select id="selectRoleById" resultType="com.gacfox.demo.model.Role">
        SELECT role_id, rolename
        FROM t_role
        WHERE role_id = #{roleId}
    </select>
</mapper>

虽然嵌套查询必须写resultMap,但实际上也是可以自动映射的。

<?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.gacfox.demo.mapper.UserMapper">
    <resultMap id="userMap" type="com.gacfox.demo.model.User">
        <association property="role" column="{roleId=role_id}"
                     select="com.gacfox.demo.mapper.RoleMapper.selectRoleById"/>
    </resultMap>
    <select id="selectUserById" resultMap="userMap">
        SELECT user_id, username, email, password, role_id
        FROM t_user
        WHERE user_id = #{id}
    </select>
</mapper>

对于resultMap中没有写出的字段,MyBatis会尝试进行自动映射。

一对多查询

一对多写法和一对一差不多,只不过把<association>标签换成了<collection>标签。下面例子中,用户和角色具有一对多关系,一个用户可以有多个角色。由于写法和之前类似,这里就不展示连接查询了,下面是嵌套查询的例子。

CREATE TABLE t_user (
    user_id BIGINT AUTO_INCREMENT,
    username VARCHAR(255) NOT NULL,
    email VARCHAR(255) NOT NULL,
    password VARCHAR(255) NOT NULL,
    PRIMARY KEY (user_id)
);
CREATE TABLE t_role (
    role_id BIGINT AUTO_INCREMENT,
    rolename VARCHAR(255) NOT NULL,
    user_id BIGINT,
    PRIMARY KEY (role_id),
    FOREIGN KEY (user_id)
        REFERENCES t_user (user_id)
        ON DELETE SET NULL ON UPDATE CASCADE
);

实体类User需要改为以下形式。

@Data
public class User {
    private Long userId;
    private String username;
    private String email;
    private String password;
    private List<Role> roleList;
}

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.gacfox.demo.mapper.UserMapper">
    <resultMap id="userMap" type="com.gacfox.demo.model.User">
        <collection property="roleList" column="{userId=userId}"
                    select="com.gacfox.demo.mapper.RoleMapper.selectRolesByUserId" fetchType="lazy"/>
    </resultMap>
    <select id="selectUserById" resultMap="userMap">
        SELECT user_id, username, email, password
        FROM t_user
        WHERE user_id = #{id}
    </select>
</mapper>

RoleMapper.java

List<Role> selectRolesByUserId(Long userId);

RoleMapper.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.gacfox.demo.mapper.RoleMapper">
    <select id="selectRolesByUserId" resultType="com.gacfox.demo.model.Role">
        SELECT role_id, rolename
        FROM t_role
        WHERE user_id = #{userId}
    </select>
</mapper>

使用延迟加载

MyBatis使用嵌套查询时支持延迟加载,但默认未开启,延迟加载需要在mybatis-config.xml中按如下配置。

<settings>
  <setting name="lazyLoadingEnabled" value="true" />
</settings>

此外在XML映射配置中,我们还需要在<association><collection>标签中配置fetchType=lazy,这样对应的嵌套查询就可以使用懒加载了。

<association property="role" column="{roleId=role_id}" select="com.gacfox.demo.mapper.RoleMapper.selectRoleById" fetchType="lazy"/>
  • fetchType:如果设置为lazy就会使用懒加载。

关于多对多查询

对于MyBatis,一对多和多对多原理是一样的,使用连接查询或嵌套查询组合好<collection>标签即可实现,这里就不赘述了。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。