Mybatis之resultMap详解
resultMap作用是处理数据表中字段与java实体类中属性的映射关系。
准备工作
① 创建数据库&数据表
CREATE DATABASE `dbtest1`;
CREATE TABLE `t_emp` (
`emp_id` int NOT NULL AUTO_INCREMENT,
`emp_name` varchar(20) DEFAULT NULL,
`age` int DEFAULT NULL,
`gender` char(1) DEFAULT NULL,
`dept_id` int DEFAULT NULL,
PRIMARY KEY (`emp_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `t_dept` (
`dept_id` int DEFAULT NULL,
`dept_name` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
② 创建实体类Emp.java
&Dept.java
public class Dept {
private int deptId;
private String deptName;
//一对多关系(一个部门对应多个员工)
private List<Emp> emps;
public Dept() {
}
public Dept(int deptId, String deptName, List<Emp> emps) {
this.deptId = deptId;
this.deptName = deptName;
this.emps = emps;
}
public int getDeptId() {
return deptId;
}
public void setDeptId(int deptId) {
this.deptId = deptId;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
public List<Emp> getEmps() {
return emps;
}
public void setEmps(List<Emp> emps) {
this.emps = emps;
}
@Override
public String toString() {
return "Dept{" +
"deptId=" + deptId +
", deptName='" + deptName + '\'' +
", emps=" + emps +
'}';
}
}
public class Emp {
private int empId;
private String empName;
private int age;
private String gender;
//多对一关系(多个员工对应一个部门)
private Dept dept;
public Emp() {
}
public Emp(int empId, String empName, int age, String gender, Dept dept) {
this.empId = empId;
this.empName = empName;
this.age = age;
this.gender = gender;
this.dept = dept;
}
public int getEmpId() {
return empId;
}
public void setEmpId(int empId) {
this.empId = empId;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public Dept getDept() {
return dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
@Override
public String toString() {
return "Emp{" +
"empId=" + empId +
", empName='" + empName + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
", dept=" + dept +
'}';
}
}
③ 创建jdbc.properties
,添加如下内容
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/dbtest1?serverTimezone=UTC
jdbc.username=root
jdbc.password=123456
jdbc.driverClassName=com.mysql.jdbc.Driver
④ 创建Mybatis核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 引入properties文件 -->
<properties resource="jdbc.properties"/>
<!-- 设置全局配置 -->
<settings>
<!-- 开启将数据表中字段名下划线命名为驼峰命名规则 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 按需加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
<!-- 设置类型别名 -->
<typeAliases>
<!-- 以包为单位,Java实体类别名为类名且不区分大小写 -->
<package name="com.evan.mybatis.entity"/>
</typeAliases>
<!-- 配置连接数据库环境 -->
<environments default="mysql8">
<!-- 连接mysql8数据库环境 -->
<environment id="mysql8">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
<!-- 连接mysql5数据库环境 -->
<environment id="mysql5">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!-- 引入mybatis映射文件 -->
<mappers>
<package name="com.evan.mybatis.mapper"/>
</mappers>
</configuration>
⑤ 添加依赖
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</dependency>
</dependencies>
⑥ 创建SqlSession工具类
public class SqlSessionUtil {
private static final Log logger = LogFactory.getLog(SqlSessionUtil.class);
public static SqlSession getSqlSession() {
SqlSession sqlSession = null;
try(InputStream is = Resources.getResourceAsStream("mybatis-config.xml");) {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
sqlSession = sqlSessionFactory.openSession(true);
} catch (IOException e) {
logger.error(e);
}
return sqlSession;
}
}
字段和属性的映射关系处理方案
数据表中字段名与属性名不一致的情况
/**
* 根据id查询员工信息
* @param empId
* @return
*/
Emp getEmpById(@Param("id") Integer empId);
- 方式一:给字段起别名,别名与属性名一致
<select id="getEmpById" resultType="com.evan.mybatis.entity.Emp">
select emp_id empId,emp_name empName,age,gender from t_emp where emp_id = #{empId}
</select>
- 方式二:设置全局配置,将字段名下换线自动映射为属性名驼峰规则
<settings>
<!-- 将数据表中字段下划线(_)映射为驼峰命名 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<select id="getEmpById" resultType="com.evan.mybatis.entity.Emp">
select emp_id ,emp_name,age,gender from t_emp where emp_id = #{empId}
</select>
- 方式三:使用
ResultMap
自定义映射处理
<!--
resultMap标签:设置自定义映射关系
属性:
id:标识自定义映射关系的唯一标识符
type:表示查询数据要映射的实体类类型
-->
<resultMap id="empResultMap" type="emp">
<!--
id标签:处理主键与实体类中id的映射关系
result标签:处理数据表中字段与实体类中属性的映射关系
属性:
column:处理映射关系中数据表中的字段名
property:处理映射关系中实体类中的属性名
jdbcType:数据表中的字段类型
javaType:实体类中的属性类型
-->
<!--
<id column="emp_id" property="empId" jdbcType="INTEGER"/>
<result column="emp_name" property="empName" jdbcType="VARCHAR"/>
<result column="age" property="age" jdbcType="INTEGER"/>
<result column="gender" property="gender" jdbcType="CHAR"/>
-->
<id column="emp_id" property="empId" javaType="java.lang.Integer"/>
<result column="emp_name" property="empName" javaType="java.lang.String"/>
<result column="age" property="age" javaType="java.lang.Integer"/>
<result column="gender" property="gender" javaType="java.lang.String"/>
</resultMap>
<select id="getEmpById" resultMap="empResultMap">
select emp_id,emp_name,age,gender
from t_emp
where emp_id = #{id}
</select>
结论:
若字段名和实体类中的属性名不一致,但是字段名符合数据库的规则(使用),实体类中的属性名符合Java的规则(使用驼峰)。
此时也可通过以下两种方式处理字段名和实体类中的属性的映射关系。
- 可以通过为字段起别名的方式,保证和实体类中的属性名保持一致。
- 可以在MyBatis的核心配置文件中设置一个全局配置信息
mapUnderscoreToCamelCase
,可以在查询表中数据时,自动将下划线类型的字段名转换为驼峰。
例如:字段名user_name
,设置了mapUnderscoreToCamelCase
,此时字段名就会转换为userName
。
测试
@Test
public void test1() {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
System.out.println(mapper.getEmpById(5));
sqlSession.close();
}
多对一的关系映射处理
查询员工信息以及员工所对应的部门信息。
- 方式一: 级联属性赋值处理
/**
* 获取员工及所对应的部门信息
* @param empId
* @return
*/
Emp getEmpAndDeptById(@Param("empId") Integer empId);
<!-- 多对一映射关系:级联属性赋值 -->
<resultMap id="empAndDeptResultMap" type="emp">
<id column="emp_id" property="empId" javaType="java.lang.Integer"/>
<result column="emp_name" property="empName" javaType="java.lang.String"/>
<result column="age" property="age" javaType="java.lang.Integer"/>
<result column="gender" property="gender" javaType="java.lang.String"/>
<result column="dept_id" property="dept.deptId" javaType="java.lang.Integer"/>
<result column="dept_name" property="dept.deptName" javaType="java.lang.String"/>
</resultMap>
<select id="getEmpAdnDeptById" resultMap="empAndDeptResultMap">
SELECT
t_emp.*,t_dept.`dept_name`
FROM
t_emp LEFT JOIN t_dept ON t_emp.`dept_id`=t_dept.`dept_id`
WHERE
t_emp.emp_id = #{id}
</select>
- 方式二:使用
<association>
标签
<!-- 多对一映射关系:association标签 -->
<resultMap id="empAndDeptResultMap" type="emp">
<id column="emp_id" property="empId" javaType="java.lang.Integer"/>
<result column="emp_name" property="empName" javaType="java.lang.String"/>
<result column="age" property="age" javaType="java.lang.Integer"/>
<result column="gender" property="gender" javaType="java.lang.String"/>
<!--
association:处理多对一的映射关系(处理实体类类型的属性)
property:设置需要处理映射关系的属性的属性名
javaType:设置需要处理的属性的类型
-->
<association property="dept" javaType="dept">
<id column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
</association>
</resultMap>
<select id="getEmpAdnDeptById" resultMap="empAndDeptResultMap">
SELECT
t_emp.*,t_dept.`dept_name`
FROM
t_emp LEFT JOIN t_dept ON t_emp.`dept_id`=t_dept.`dept_id`
WHERE
t_emp.emp_id = #{id}
</select>
测试
@Test
public void test2() {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
System.out.println(mapper.getEmpAdnDeptById(2));
sqlSession.close();
}
- 方式三:使用
<association>
标签进行分步查询
Step 1:查询员工信息
public interface EmpMapper {
/**
* 第一步:通过分步查询员工及所对应的部门信息
* @param empId
* @return
*/
Emp getEmpAndDeptByStepOne(@Param("empId") Integer empId);
}
Step 2:查询部门信息
public interface DeptMapper {
/**
* 第二步:通过分步查询员工及所对应的部门信息
* @param deptId
* @return
*/
Dept getEmpAndDeptByStepTwo(@Param("deptId") Integer deptId);
}
<!-- 部门信息查询 -->
<select id="getEmpAndDeptByStepTwo" resultType="com.evan.mybatis.entity.Dept">
select * from t_dept where dept_id = #{deptId}
</select>
<!-- 多对一映射关系: 分步查询 -->
<resultMap id="empAndDeptByStepResultMap" type="emp">
<id column="emp_id" property="empId"/>
<result column="emp_name" property="empName"/>
<result column="age" property="age"/>
<result column="gender" property="gender"/>
<!--
select设置分步查询的sql唯一标识(namespace.SQLId或mapper接口的全类名.方法名)
column:将查询出的某个字段作为分步查询的sql条件
fetchType:当开启了全局延迟加载之后,通过该属性设置当前分步查询是否使用延迟加载
lazy:表示延迟加载
eager:表示立即加载
-->
<association property="dept" javaType="dept" fetchType="lazy"
select="com.evan.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo"
column="dept_id"/>
</resultMap>
<select id="getEmpAndDeptByStepOne" resultMap="empAndDeptByStepResultMap">
SELECT * FROM t_emp WHERE emp_id = #{empId}
</select>
在Mybatis核心配置文件中设置全局延迟加载:
<settings>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!--
按需加载:
当开启延迟加载时同时设置该属性是否按需加载,默认true按需加载
若为false:加载全部字段信息,不管你是否需要这个字段信息
若为true:按需加载,按照需要的字段查询加载
-->
<setting name="aggressiveLazyLoading" value="true"/>
</settings>
结论:
分步查询的优点:可以实现延迟加载,但是必须在核心配置文件中设置全局配置信息:
lazyLoadingEnabled
:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。
aggressiveLazyLoading
:当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个属性会按需加载。
此时就可以实现按需加载,获取的数据是什么,就只会执行相应的sql。此时可通过association
和collection中
的fetchType
属性设置当前的分步查询是否使用延迟加载,fetchType="lazy(延迟加载)|eager(立即加载)"
测试
@Test
public void test3() {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
System.out.println(mapper.getEmpAndDeptByStepOne(5));
sqlSession.close();
}
一对多的关系映射处理
- 方式一:使用
<conllection>
标签
/**
* 根据部门id查询部门以及部门中所对应的员工信息
* @param deptId
* @return
*/
Dept getDeptAndEmpById(@Param("deptId") Integer deptId);
<!-- 一对多映射关系: 使用<collection>标签 -->
<resultMap id="deptAndEmpResultMap" type="dept">
<id column="dept_id" property="deptId" javaType="java.lang.Integer"/>
<result column="dept_name" property="deptName" javaType="java.lang.String"/>
<!--
collection: 处理一对多的映射关系
ofType:设置集合类型属性中存储的数据类型
-->
<collection property="emps" ofType="emp">
<id column="emp_id" property="empId" javaType="java.lang.Integer"/>
<result column="emp_name" property="empName" javaType="java.lang.String"/>
<result column="age" property="age" javaType="java.lang.Integer"/>
<result column="gender" property="gender" javaType="java.lang.String"/>
</collection>
</resultMap>
<select id="getDeptAndEmpById" resultMap="deptAndEmpResultMap">
SELECT
t_emp.*,t_dept.`dept_name`
FROM t_dept
LEFT JOIN t_emp ON t_dept.`dept_id` = t_emp.`dept_id`
WHERE t_dept.`dept_id` = #{deptId}
</select>
- 方式二:分步查询
/**
* 分步查询第一步:根据id查询部门信息
* @param deptId
* @return
*/
Dept getDeptById(@Param("deptId") Integer deptId);
/**
* 分步查询第二步: 根据部门id查询部门所属员工
* @param deptId
* @return
*/
List<Emp> getEmpListByDeptId(Integer deptId);
<select id="getEmpListByDeptId" resultType="com.evan.mybatis.entity.Emp">
select * from t_emp where dept_id = #{deptId}
</select>
<!-- 一对多映射关系: 分步查询 -->
<!--
分步查询步骤:
先根据id查询一对多关系主表的信息,然后将查询出的主表id作为查询条件,去找<select>标签中的查询信息,并将两个分步查询处理的信息按照<property>标签的属性类型接收
说明:
property="emps":emps是实体类中的属性名,由于emps属性类型是list集合,所以<select>中的
查询方法返回类型也是使用List集合
select、property、column三者之间的关系:
根据select的column查询出来的结果,封装到property,显示最终结果集
-->
<resultMap id="deptAndEmpByStepResultMap" type="dept">
<id column="dept_id" property="deptId" javaType="java.lang.Integer"/>
<result column="dept_name" property="deptName" javaType="java.lang.String"/>
<collection property="emps" column="dept_id"
select="com.evan.mybatis.mapper.EmpMapper.getEmpListByDeptId"/>
</resultMap>
<select id="getDeptById" resultMap="deptAndEmpByStepResultMap">
select * from t_dept where dept_id = #{deptId}
</select>
测试
- 分步查询测试
@Test
public void test5() {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
Dept dept = mapper.getDeptById(2);
System.out.println(dept);
sqlSession.close();
}
测试结果:
结果可以看到,多条查询语句执行。
- 按需查询
@Test
public void test5() {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
Dept dept = mapper.getDeptById(2);
System.out.println(dept.getDeptName());
sqlSession.close();
}
测试结果:
开启延迟加载以后,可以看到按照需求查询指定SQL执行。