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 {
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
中的属性名对应,除此之外,因为别名中包含了点.
,所以需要用双引号括起来。
一对一连接查询(使用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.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会尝试进行自动映射。
一对多嵌套查询
一对多写法和一对一差不多,只不过把<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>
对于SpringBoot开启懒加载的写法如下。
mybatis.configuration.lazy-loading-enabled=true
此外,在XML映射配置中,我们还需要在<association>
或<collection>
标签中配置fetchType=lazy
,这样对应的嵌套查询就可以使用懒加载了。
<association property="role" column="{roleId=role_id}" select="com.gacfox.demomb.mapper.RoleMapper.queryRoleById" fetchType="lazy"/>
fetchType
:如果设置为lazy
,就会使用懒加载的方式。
关于多对多查询
对于MyBatis,一对多和多对多原理是一样的,使用连接查询或嵌套查询组合好<collection>
标签即可实现,这里就不赘述了。