一、<resultMap> 元素
<resultMap> 元素表示结果映射集,是 MyBatis 中最重要也是最强大的元素,主要用来定义映射规则、级联的更新以及定义类型转化器等。其基本结构如下:
<resultMap id="" type="">
<constructor><!-- 类再实例化时用来注入结果到构造方法 -->
<idArg/><!-- ID参数,结果为ID -->
<arg/><!-- 注入到构造方法的一个普通结果 -->
</constructor>
<id/><!-- 用于表示哪个列是主键 -->
<result/><!-- 注入到字段或JavaBean属性的普通结果 -->
<association property=""/><!-- 用于一对一关联 -->
<collection property=""/><!-- 用于一对多、多对多关联 -->
<discriminator javaType=""><!-- 使用结果值来决定使用哪个结果映射 -->
<case value=""/><!-- 基于某些值的结果映射 -->
</discriminator>
</resultMap>
<resultMap> 元素与<resultType>元素的区别:
相同点:都是用于select语句的返回类型。
不同:
resultType 是直接表示返回类型的(一般返回为基本数据类型时使用,当查询的是一条SQL数据时,且这条SQL数据的每个字段都和一个Javabean 中的属性名 与之对应,mybatis 会通过 autoMapping ,将每个字段的值赋给 Javabean),而当字段名和属性名不一致时,这时可以使用 resultMap
resultMap 是对外部的一个 ResultMap 标签的引用,并且 resultType 跟 resultMap 不能同时存在。
resultMap的基本使用:
<resultMap type="com.day1.entity.Student" id="stuMap">
<result column="username" property="username"/>
<result column="age" property="age"/>
<result column="address" property="address"/>
</resultMap>
<select id="selectStudent" resultMap="stuMap" parameterType="int">
select * from t_student where id=#{id}
</select>
当字段名和属性名不一致时,可以使用 resultMap 。假设Student类中的属性名分别为:s_name、s_age、s_address,则上述代码应修改为:
<resultMap type="com.day1.entity.Student" id="stuMap">
<result column="username" property="s_name"/>
<result column="age" property="s_age"/>
<result column="address" property="s_address"/>
</resultMap>
<select id="selectStudent" resultMap="stuMap" parameterType="int">
select * from t_student where id=#{id}
</select>
二、一对一关联查询
一对一关系在现实生活中是十分常见的,例如一个人只有一个身份证、一个员工只属于一个部门。在 MyBatis 中,通过 <resultMap> 元素的子元素 <association> 处理这种一对一级联关系。
在 <association> 元素中通常使用以下属性。
- property:指定映射到实体类的对象属性。
- column:指定表中对应的字段(即查询返回的列名)。
- javaType:指定映射到实体对象属性的类型。
- select:指定引入嵌套查询的子 SQL 语句,该属性用于关联映射中的嵌套查询。
一对一关联有三种方式:
(1)嵌套查询,执行两个SQL语句
(2)嵌套结果,执行一个SQL语句
(3)连接查询,使用POJO存储结果
接下来就对上述三种方式进行演示。
方式一:嵌套查询,执行两个SQL语句
(1)创建部门表和员工表
CREATE TABLE `t_dept` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`dept_name` varchar(20) DEFAULT NULL,
`dept_desc` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
);
insert into t_dept(dept_name, dept_age) values("开发部","软件开发");
insert into t_dept(dept_name, dept_age) values("测试部","软件测试");
insert into t_dept(dept_name, dept_age) values("财务部","发放钱财");
insert into t_dept(dept_name, dept_age) values("人事部","招聘员工");
CREATE TABLE `t_emp` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`emp_name` varchar(20) DEFAULT NULL,
`emp_age` int(11) DEFAULT NULL,
`dept_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `dept_id` (`dept_id`),
CONSTRAINT `t_emp_ibfk_1` FOREIGN KEY (`dept_id`) REFERENCES `t_dept` (`id`)
);
INSERT INTO `t_emp` VALUES (1,'张倩',22,2),(2,'杨帆',24,1),(3,'章飞',21,3),(4,'李冰',51,4),(5,'王猛',36,3),(6,'康生',26,1),(7,'刘婷',20,3),(8,'刘羽',22,2);
(2) 创建实体类部门类Dept和员工类Emp。
部门类Dept:
public class Dept {
private Integer id;
private String deptName;
private String deptDesc;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
public String getDeptDesc() {
return deptDesc;
}
public void setDeptDesc(String deptDesc) {
this.deptDesc = deptDesc;
}
@Override
public String toString() {
return "Dept{" +
"id=" + id +
", deptName='" + deptName + '\'' +
", deptDesc='" + deptDesc + '\'' +
'}';
}
}
员工类Emp:
public class Emp {
private Integer id;
private String empName;
private String empAge;
private Dept dept;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public String getEmpAge() {
return empAge;
}
public void setEmpAge(String empAge) {
this.empAge = empAge;
}
public Dept getDept() {
return dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
@Override
public String toString() {
return "Emp{" +
"id=" + id +
", empName='" + empName + '\'' +
", empAge='" + empAge + '\'' +
", dept=" + dept +
'}';
}
}
(3)创建IDeptDao和IEmpDao
IDeptDao.java:
public interface IDeptDao {
/**
* 根据id查询部门信息
* @param id id值
* @return
*/
Dept selectDeptById(int id);
}
IEmpDao.java文件:
public interface IEmpDao {
/**
* 根据id查询员工信息
* @param id id值
* @return
*/
Emp selectEmpById(int id);
}
(4)创建deptMapper.xml和empMapper.xml文件
deptMapper.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">
<!--namespace:指定了唯一的命名空间-->
<mapper namespace="com.day1.dao.IDeptDao">
<!-- 这里使用resultMap是为了解决数据库列名和实体类属性名不一致的问题 -->
<resultMap type="com.day1.entity.Dept" id="deptProps">
<id property="id" column="id"/>
<result property="deptName" column="dept_name"/>
<result property="deptDesc" column="dept_desc"/>
</resultMap>
<select id="selectDeptById" parameterType="int" resultMap="deptProps">
select * from t_dept where id = #{id}
</select>
</mapper>
empMapper.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">
<!--namespace:指定了唯一的命名空间-->
<mapper namespace="com.day1.dao.IEmpDao">
<resultMap type="com.day1.entity.Emp" id="empAndDept">
<id property="id" column="id"/>
<result property="empName" column="emp_name"/>
<result property="empAge" column="emp_age"/>
<!-- 一对一级联查询-->
<association property="dept" column="dept_id" javaType="com.day1.entity.Dept"
select="com.day1.dao.IDeptDao.selectDeptById"/>
</resultMap>
<select id="selectEmpById" parameterType="int" resultMap="empAndDept">
select * from t_emp where id = #{id}
</select>
</mapper>
(5)在SqlMapperConfig.xml中配置mapper文件
<!-- 指定映射的配置文件位置:每一个都是独立的文件 -->
<mappers>
<mapper resource="com/day1/deptMapper.xml"></mapper>
<mapper resource="com/day1/empMapper.xml"></mapper>
</mappers>
(6)进行测试。
@Test
public void testSelect01() throws IOException {
//1、读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapperConfig.xml");
//2、创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3、使用工厂生产SqlSession对象
SqlSession sqlSession = factory.openSession();
//4、使用SqlSession创建dao接口的代理对象
IEmpDao empDao = sqlSession.getMapper(IEmpDao.class);
//5、使用代理对象执行方案
Emp emp = empDao.selectEmpById(2);
System.out.println(emp);
//6、释放资源
sqlSession.close();
in.close();
}
方式二:嵌套结果,执行一个SQL语句
方式二不需要使用deptMapper.xml文件中的sql语句,而是在empMapper.xml文件中通过一条sql语句完成查询操作。修改上述查询语句如下:
<resultMap type="com.day1.entity.Emp" id="empAndDept">
<id property="id" column="id"/>
<result property="empName" column="emp_name"/>
<result property="empAge" column="emp_age"/>
<!-- 一对一级联查询-->
<association property="dept" javaType="com.day1.entity.Dept">
<!-- 此处id代表的是dept的id -->
<id property="id" column="id"/>
<result property="deptName" column="dept_name"/>
<result property="deptDesc" column="dept_desc"/>
</association>
</resultMap>
<select id="selectEmpById" parameterType="int" resultMap="empAndDept">
select e.*, d.dept_name, d.dept_desc from t_emp e, t_dept d where e.id = #{id} and e.dept_id = d.id;
</select>
方式三:连接查询,使用POJO存储结果
方式三需要提供一个Emp的扩展类,如下:
public class EmpExtend{
private Integer id;
private String empName;
private String empAge;
private String deptName;
private String deptDesc;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public String getEmpAge() {
return empAge;
}
public void setEmpAge(String empAge) {
this.empAge = empAge;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
public String getDeptDesc() {
return deptDesc;
}
public void setDeptDesc(String deptDesc) {
this.deptDesc = deptDesc;
}
@Override
public String toString() {
return "EmpExtend{" +
"id=" + id +
", empName='" + empName + '\'' +
", empAge='" + empAge + '\'' +
", deptName='" + deptName + '\'' +
", deptDesc='" + deptDesc + '\'' +
'}';
}
}
修改IEmpDao的方法返回值如下:
EmpExtend selectEmpById(int id);
修改empMapper.xml中的映射语句如下:
<resultMap type="com.day1.entity.EmpExtend" id="empExtend">
<id property="id" column="id"/>
<result property="empName" column="emp_name"/>
<result property="empAge" column="emp_age"/>
<result property="deptName" column="dept_name"/>
<result property="deptDesc" column="dept_desc"/>
</resultMap>
<select id="selectEmpById" parameterType="int" resultMap="empExtend">
select e.*, d.dept_name, d.dept_desc from t_emp e, t_dept d where e.id = #{id} and e.dept_id = d.id;
</select>
修改测试方法如下:
@Test
public void testSelect01() throws IOException {
//1、读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapperConfig.xml");
//2、创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3、使用工厂生产SqlSession对象
SqlSession sqlSession = factory.openSession();
//4、使用SqlSession创建dao接口的代理对象
IEmpDao empDao = sqlSession.getMapper(IEmpDao.class);
//5、使用代理对象执行方案
EmpExtend empExtend = empDao.selectEmpById(2);
System.out.println(empExtend);
//6、释放资源
sqlSession.close();
in.close();
}
三种方式的对比:
方式一采用了分文件的方式执行了两次sql语句,可重用性比较好。
方式二在一个mapper文件中就可以完成查询操作,执行了一次sql语句。
方式三尽管可以实现功能,但是需要提供扩展类,会出现大量的冗余代码,造成开发的复杂度增大和可重用性降低。