持久层关联查询是一个比较复杂的问题,每个框架的学习笔记中都会专门列出一篇进行说明。这里以例子的方式,说明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>
标签即可实现。