• 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语句,所以肯定会导致查询的效率变低,但是这种方式也有好处,那就是可以延迟加载。

  • 相关阅读:
    com.alibaba.fastjson.JSONException: default constructor not found. class ……
    ActiveMQ伪集群部署
    #{}和${}的区别
    微信小程序——报错汇总
    PHP——base64的图片的另类上传方法
    PHP——base64的图片转为文件图片
    veu——引入iconfont图标
    vue——script内容详解
    webpack——阮一峰webpackDemo分析
    webpack——快速入门【一】
  • 原文地址:https://www.cnblogs.com/tanghaorong/p/13966084.html
Copyright © 2020-2023  润新知