Hey, Nice to meet You. 

必有过人之节.人情有所不能忍者,匹夫见辱,拔剑而起,挺身而斗,此不足为勇也,天下有大勇者,猝然临之而不惊,无故加之而不怒.此其所挟持者甚大,而其志甚远也.          ☆☆☆所谓豪杰之士,

Mybatis3详解(八)----高级映射之一对一映射

1、前言

在前面SQL映射文件的介绍中,说到resultMap元素中有两个标签是用来做关联查询操作的,也就是一对一,一对多,对应到Mybatis中的标签分别是association和collection标签。它们在实际的项目中,会经常用到关联表的查询,因为实际的项目中不可能是对单表的查询,经常会有一对一,一对多等情况,我们可以使用这两个标签来配合实现。在Java实体对象中,一对一属性使用包装对象来实现,一对多属性使用List或者Set来实现。

association和collection二者标签的内部属性基本是一致的,它们的属性介绍如下(红色标注表示常用):

  • property:映射实体类属性名。
  • column:映射数据库字段名或者其别名(这个别名是数据库起的,如 select username as name)。
  • javaType:映射java类型。
  • jdbcType:映射数据库类型。
  • ofType:映射集合的类型(注意:javaType是用来指定pojo中属性的类型,而ofType指定的是映射到list集合属性中pojo的类型,也就是尖括号的泛型private List<User> users)。
  • select:用于加载复杂类型属性的映射语句的id (全限定名加方法,方法名后面无括号,例如:com.thr.mapper.UserMapper.selectAllUser),它会从 column 属性指定的列中检索数据,作为参数传递给目标 select 语句。 具体请参考下面的第二个例子。
  • fetchType:延迟加载,lazy打开延迟加载,eager积极加载。指定属性后,将在映射中忽略全局配置参数 lazyLoadingEnabled,使用属性的值。
  • resultMap:不使用嵌套模式,而是将此关联的嵌套结果集映射到一个外部的<resultMap>标签中,然后通过 id 进行引入。
  • resultSet:指定用于加载复杂类型的结果集名字。
  • autoMapping:自动封装,如果数据库字段和javaBean的字段名一样,可以使用这种方式,但是不建议采取,还是老老实实写比较稳妥,如果非要使用此功能,那就在全局配置中加上mapUnderscoreToCamelCase=TRUE,它会使经典数据库字段命名规则翻译成javaBean的经典命名规则,如:a_column翻译成aColumn。
  • columnPrefix:关联多张表查询时,为了使列明不重复,使用此功能可以减少开发量。
  • foreignColumn:指定外键对应的列名,指定的列将与父类型中 column 的给出的列进行匹配。
  • notNullColumn:不为空的列,如果指定了列,那么只有当字段不为空时,Mybatis才会真正创建对象,才能得到我们想要的值。
  • typeHandler:数据库与Java类型匹配处理器(可以参考前面的TypeHandler部分)。

如果你想对这些属性有更加深入了解的话可以自行去参考Mybatis的官方文档,链接:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html

2、案例分析

我们以典型的 员工(Employee)和部门(Department)为例:

  1. 一个员工只能在一个部门;Employee—>Department(一对一)
  2. 一个部门可以包含多个员工;Department—>Employee(一对多)

下面我们分别创建 员工表:t_employee 和 部门表:t_department:

image

对应mysql的sql脚本如下:

-- ----------------------------
-- Table structure for t_department
-- ----------------------------
DROP TABLE IF EXISTS `t_department`;
CREATE TABLE `t_department`  (
  `department_id` int(11) NOT NULL AUTO_INCREMENT,
  `department_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`department_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_department
-- ----------------------------
INSERT INTO `t_department` VALUES (1, '开发部');
INSERT INTO `t_department` VALUES (2, '人力资源部');
INSERT INTO `t_department` VALUES (3, '市场营销部');
INSERT INTO `t_department` VALUES (4, '财务部');
INSERT INTO `t_department` VALUES (5, '行政部');
INSERT INTO `t_department` VALUES (6, '监察部');
INSERT INTO `t_department` VALUES (7, '客服服务部');

-- ----------------------------
-- Table structure for t_employee
-- ----------------------------
DROP TABLE IF EXISTS `t_employee`;
CREATE TABLE `t_employee`  (
  `employee_id` int(11) NOT NULL AUTO_INCREMENT,
  `employee_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `employee_age` int(255) NULL DEFAULT NULL,
  `employee_sex` int(255) NULL DEFAULT NULL,
  `employee_email` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `employee_address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `department_id` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`employee_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_employee
-- ----------------------------
INSERT INTO `t_employee` VALUES (1, '唐浩荣', 23, 1, '15477259875@163.com', '中国上海浦东区', 1);
INSERT INTO `t_employee` VALUES (2, '黄飞鸿', 32, 1, '86547547@qq.com', '大清广东', 2);
INSERT INTO `t_employee` VALUES (3, '十三姨', 18, 0, '520520520@gmail.com', '大清广东', 3);
INSERT INTO `t_employee` VALUES (4, '纳兰元述', 28, 1, '545627858@qq.com', '大清京师', 5);
INSERT INTO `t_employee` VALUES (5, '梁宽', 31, 1, '8795124578@qq.com', '大清广东', 7);
INSERT INTO `t_employee` VALUES (6, '蔡徐坤', 20, 0, '4257895124@gmail.com', '四川成都', 4);
INSERT INTO `t_employee` VALUES (7, '杨超越', 21, 0, '8746821252@qq.com', '中国北京', 7);
INSERT INTO `t_employee` VALUES (8, '马保国', 66, 1, '6666666666@qq.com', '广东深圳', 6);
INSERT INTO `t_employee` VALUES (9, '马牛逼', 45, 1, 'asdfg45678@163.com', '湖北武汉', 3);

注意:在MyBatis中主要有这两种方式实现关联查询:

  • 嵌套结果:使用嵌套映射的方式来处理关联结果的子集。
  • 分步查询:通过 select 属性来执行另外一个 SQL 映射语句来返回预期的复杂类型。select属性的规则是全限定名加方法名,例如:com.thr.mapper.UserMapper.selectAllUser,方法名后面无括号。

所以下面我们就通过代码来学习这两种方式实现Mybatis的关联查询。

3、嵌套结果

①、分别定义Employee和Department实体类

Employee实体类:

/**
 * 员工实体类
 */
public class Employee {
    //员工id
    private Integer empId;
    //员工名称
    private String empName;
    //员工年龄
    private Integer empAge;
    //员工性别
    private Integer empSex;
    //员工邮箱
    private String empEmail;
    //员工地址
    private String empAddress;

    //员工所属部门,和部门表构成一对一的关系,一个员工只能在一个部门
    private Department department;

    //getter、setter、toString方法和一些构造方法省略...
}

Department实体类:

/**
 * 部门实体类
 */
public class Department {
    //部门id
    private Integer deptId;
    //部门名称
    private String deptName;

    //getter、setter、toString方法和一些构造方法省略...
}

②、创建EmployeeMapper接口和EmployeeMapper.xml 文件

EmployeeMapper接口:

/**
 * 员工Mapper接口
 */
public interface EmployeeMapper {
    //查询所有数据
    List<Employee> selectAll();
    //根据员工id查询数据
    Employee selectEmpByEmpId(@Param("id") Integer empId);
}

EmployeeMapper.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.thr.mapper.EmployeeMapper">
    <resultMap id="employeeMap" type="com.thr.pojo.Employee">
        <id property="empId" column="employee_id"/>
        <result property="empName" column="employee_name"/>
        <result property="empAge" column="employee_age"/>
        <result property="empSex" column="employee_sex"/>
        <result property="empEmail" column="employee_email"/>
        <result property="empAddress" column="employee_address"/>
        <!-- 一对一关联对象 -->
        <association property="department" javaType="department">
            <id property="deptId" column="department_id"/>
            <result property="deptName" column="department_name"/>
        </association>
    </resultMap>

    <!-- 查询所有数据-->
    <select id="selectAll" resultMap="employeeMap">
        SELECT * FROM
        t_employee e,
        t_department d
        where
        e.department_id=d.department_id
    </select>

    <!--根据员工id查询数据-->
    <select id="selectEmpByEmpId" parameterType="int" resultMap="employeeMap">
        SELECT * FROM
        t_employee e,
        t_department d
        where
        e.department_id=d.department_id
        and e.employee_id= #{id}
    </select>
</mapper>

③、创建数据库连接文件和日志文件

db.properties文件:

#数据库连接配置
database.driver=com.mysql.cj.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8
database.username=root
database.password=root

log4j.properties文件:

log4j.rootLogger=DEBUG, Console
#Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
log4j.logger.java.sql.ResultSet=INFO
log4j.logger.org.apache=INFO
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG 

④、注册 EmployeeMapper.xml 文件

<?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="db.properties"/>
    <!--配置别名-->
    <typeAliases>
        <!-- 对包进行扫描,可以批量进行别名设置,设置规则是:获取类名称,将其第一个字母变为小写 -->
        <package name="com.thr.pojo"/>
    </typeAliases>
    <!-- 配置环境.-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${database.driver}"/>
                <property name="url" value="${database.url}"/>
                <property name="username" value="${database.username}"/>
                <property name="password" value="${database.password}"/>
            </dataSource>
        </environment>
    </environments>

    <!--注册mapper,通过扫描的方式-->
    <mappers>
        <package name="com.thr.mapper"/>
    </mappers>
</configuration>

⑤、编写测试代码

package com.thr.test;

import com.thr.mapper.EmployeeMapper;
import com.thr.pojo.Employee;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

/**
 * 测试代码
 */
public class MybatisTest {
    //定义 SqlSession
    private SqlSession sqlSession = null;
    //定义 EmployeeMapper对象
    private EmployeeMapper mapper = null;

    @Before//在测试方法执行之前执行
    public void getSqlSession(){
        //1、加载 mybatis 全局配置文件
        InputStream is = MybatisTest.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
        //2、创建SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        //3、根据 sqlSessionFactory 产生 session
        sqlSession = sqlSessionFactory.openSession();
        //4、创建Mapper接口的的代理对象,getMapper方法底层会通过动态代理生成UserMapper的代理实现类
        mapper = sqlSession.getMapper(EmployeeMapper.class);
    }

    @After//在测试方法执行完成之后执行
    public void destroy() throws IOException {
        sqlSession.commit();
        sqlSession.close();

    }
    //查询所有数据
    @Test
    public void testSelectAll(){
        List<Employee> employees = mapper.selectAll();
        for (Employee employee : employees) {
            System.out.println(employee);
        }
    }

    //根据员工id查询数据
    @Test
    public void testSelectEmpByEmpId(){
        Employee employee = mapper.selectEmpByEmpId(1);
        System.out.println(employee);
    }
}

 ⑥、运行结果

       项目整体目录:

image

查询所有数据:

image

根据员工id查询数据:

image

4、分步查询

分步查询的这种方式是通过association标签中的select属性来完成,它需要执行另外一个 SQL 映射语句来返回预期的复杂类型,并且会从 column 属性指定的列中检索数据,作为参数传递给目标 select 语句。所以我们必须在关联的另一个Mapper接口中创建一个根据 id 查询数据的方法,并且表字段和实体属性如果不同还要进行映射。

①、更改EmployeeMapper.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.thr.mapper.EmployeeMapper">
    <resultMap id="employeeMap" type="com.thr.pojo.Employee">
        <id property="empId" column="employee_id"/>
        <result property="empName" column="employee_name"/>
        <result property="empAge" column="employee_age"/>
        <result property="empSex" column="employee_sex"/>
        <result property="empEmail" column="employee_email"/>
        <result property="empAddress" column="employee_address"/>
        <!-- 一对一关联对象,注意:select方式需要加column属性,column属性会从当前查询出的指定列检索数据,
		     这里为t_employee表中的department_id,然后作为参数传递给目标的select语句-->
        <association property="department" column="department_id" javaType="department"
                     select="com.thr.mapper.DepartmentMapper.selectDeptByDeptId"/>
    </resultMap>

    <!-- 查询所有数据-->
    <select id="selectAll" resultMap="employeeMap">
      SELECT * FROM t_employee
    </select>

    <!--根据员工id查询数据-->
    <select id="selectEmpByEmpId" parameterType="int" resultMap="employeeMap">
        SELECT * FROM t_employee where employee_id= #{id}
    </select>
</mapper>

②、创建DepartmentMapper和DepartmentMapper.xml文件

DepartmentMapper接口:

/**
 * 部门Mapper接口
 */
public interface DepartmentMapper {
    //根据部门id查询数据
    Department selectDeptByDeptId(@Param("id") Integer deptId);
}

DepartmentMapper.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.thr.mapper.DepartmentMapper">
    <resultMap id="departmentMap" type="com.thr.pojo.Department">
        <id property="deptId" column="department_id"/>
        <result property="deptName" column="department_name"/>
    </resultMap>

    <!--根据部门id查询-->
    <select id="selectDeptByDeptId" parameterType="int" resultMap="departmentMap">
        select * from t_department where department_id = #{id}
    </select>
</mapper>

③、运行结果

查询所有数据:

image

根据员工id查询数据:

image

可以发现使用这种方式明显多执行了很多SQL语句,所以肯定会导致查询的效率变低,但是这种方式也有好处,那就是可以延迟加载。

posted @ 2020-11-16 22:11  唐浩荣  阅读(1040)  评论(2编辑  收藏  举报