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 {
    public User queryUserById(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.demomb.mapper.UserMapper">
    <select id="queryUserById" resultType="com.gacfox.demomb.model.User">
        select
            u.user_id as userId,
            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中的属性名对应,除此之外,因为别名中包含了点.,所以需要用双引号括起来。

一对一连接查询手动映射

除了可以使用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.demomb.mapper.UserMapper">
    <resultMap id="userMap" type="com.gacfox.demomb.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="queryUserById" 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中定义一个queryRoleById()方法,用于嵌套查询调用。

UserMapper.java

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

RoleMapper.java

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

UserMapper.xml

<mapper namespace="com.gacfox.demomb.mapper.UserMapper">
    <resultMap id="userMap" type="com.gacfox.demomb.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.demomb.mapper.RoleMapper.queryRoleById"/>
    </resultMap>
    <select id="queryUserById" 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

<mapper namespace="com.gacfox.demomb.mapper.RoleMapper">
    <select id="queryRoleById" resultType="com.gacfox.demomb.model.Role">
        select role_id as roleId,rolename from t_role where role_id=#{roleId}
    </select>
</mapper>

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

<mapper namespace="com.gacfox.demomb.mapper.UserMapper">
    <resultMap id="userMap" type="com.gacfox.demomb.model.User">
        <association property="role" column="{roleId=role_id}" select="com.gacfox.demomb.mapper.RoleMapper.queryRoleById"/>
    </resultMap>
    <select id="queryUserById" resultMap="userMap">
        select user_id as userId,username,email,password,role_id from t_user where user_id=#{id}
    </select>
</mapper>

resultMap中没有写出的字段,MyBatis会尝试进行自动映射,自动映射代码简单,可读性也不差,推荐尽量使用自动映射,否则就和直接写JDBC没什么区别了,还把大量的代码放在了配置文件中,导致代码结构非常畸形。

一对多嵌套查询

一对多写法和一对一差不多,只不过把<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
);

UserMapper.xml

<mapper namespace="com.gacfox.demomb.mapper.UserMapper">
    <resultMap id="userMap" type="com.gacfox.demomb.model.User">
        <collection property="roleList" column="{userId=userId}" select="com.gacfox.demomb.mapper.RoleMapper.queryRolesByUserId" fetchType="lazy"/>
    </resultMap>
    <select id="queryUserById" resultMap="userMap">
        select user_id as userId,username,email,password from t_user where user_id=#{id}
    </select>
</mapper>

RoleMapper.java

public List<Role> queryRolesByUserId(Long userId);

RoleMapper.xml

<mapper namespace="com.gacfox.demomb.mapper.RoleMapper">
    <select id="queryRolesByUserId" resultType="com.gacfox.demomb.model.Role">
        select role_id as roleId,rolename from t_role where user_id=#{userId}
    </select>
</mapper>

使用延迟加载

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

<settings>
  <setting name="lazyLoadingEnabled" value="true" />
  <setting name="aggressiveLazyLoading" value="false" />
  <setting name="lazyLoadTriggerMethods" value=""/>
</settings>

此外,需要在<association><collection>标签中配置fetchType=lazy,这样就可以使用懒加载了。

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

关于多对多查询

一对多和多对多原理是一样的,使用连接查询或嵌套查询,组合好<collection>标签即可实现。

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