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>标签即可实现,这里就不赘述了。