MyBatis3 (3) 高级操作

891 阅读8分钟

1. 动态SQL

概念: 动态SQL指的是在SQL块中使用动态标签来完成选择判断,循环等操作。

1.1 动态条件标签

概念: 条件标签配合OGNL表达式用于判断是否允许拼接部分指定的SQL语句:

  • <if>:用于单独条件判断:
    • 使用 test 属性设置条件,OGNL表达式建议用单引号,以免冲突表达式中的字符串。
  • <choose>:用于多选一条件判断:
    • 子标签 <when> 使用 test 属性设置条件,OGNL表达式建议用单引号,以免冲突表达式中的字符串。
    • 子标签 <otherwise> 用于设置所有条件均不满足时的处理。
  • <where>:WHERE语句不为空时,生成 WHERE 关键字并删除WHERE语句中第一个 AND
  • <trim prefix="WHERE" prefixOverrides="AND|OR">:WHERE语句不为空时,生成 WHERE 前缀关键字并删除WHERE语句中第一个 ANDOR

源码: /mybatis3/

  • res: classpath:mybatis-dynamic.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 resource="jdbc/db.properties"/>

    <typeAliases>
        <package name="com.yap.pojo"/>
    </typeAliases>

    <environments default="development">

        <environment id="development">
            <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>

    </environments>

    <mappers>
        <package name="com/yap/mapper"/>
    </mappers>

</configuration>
  • src: c.y.pojo.User
/**
 * @author yap
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private Integer id;
    private String name;
    private Integer gender;
    private Integer age;
    private String info;
}
  • res: classpath:com/yap/mapper/UserMapper.xml
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.joezhou.mapper.UserMapper">

    <select id="findByIf" resultType="user">
        SELECT `id`, `name`, `gender`, `age`, `info` FROM `user` WHERE
        <if test='name != null'>
            `name` like concat('%', #{name}, '%')
        </if>
        <if test='gender != null'>
            `gender` = #{gender}
        </if>
    </select>

    <select id="findByWhere" resultType="user">
        SELECT `id`, `name`, `gender`, `age`, `info` FROM `user`
        <where>
            <if test='name != null'>
                AND `name` like concat('%', #{name}, '%')
            </if>
            <if test='gender != null'>
                AND `gender` = #{gender}
            </if>
        </where>
    </select>

    <select id="findByTrim" resultType="user">
        SELECT `id`, `name`, `gender`, `age`, `info` FROM `user`
        <trim prefix="WHERE" prefixOverrides="AND|OR">
            <if test='name != null'>
                AND `name` like concat('%', #{name}, '%')
            </if>
            <if test='gender != null'>
                AND `gender` = #{gender}
            </if>
        </trim>
    </select>

    <select id="findByChoose" resultType="user">
        SELECT `id`, `name`, `gender`, `age`, `info` FROM `user`
        <where>
            <choose>
                <when test='name != null'>
                    AND `name` like concat('%', #{name}, '%')
                </when>
                <when test='gender != null'>
                    AND `gender` = #{gender}
                </when>
                <otherwise>
                    AND `id` = #{id}
                </otherwise>
            </choose>
        </where>
    </select>
</mapper>
  • src: c.y.mapper.UserMapper

    /**
     * 根据姓名模糊以及性别精准查询用户信息
     * 若姓名为null,则按性别精准查询
     * 若性别为null,则按姓名模糊查询
     *
     * @param user 用户实体
     * @return 满足条件的用户
     */
    List<User> findByIf(User user);

    /**
     * 根据姓名模糊以及性别精准查询用户信息
     * 若姓名为null,则按性别精准查询
     * 若性别为null,则按姓名模糊查询
     * 若姓名和性别均不为null,则按姓名模糊且按性别精准查询
     * 若姓名和性别均为null,全查
     *
     * @param user 用户实体
     * @return 满足条件的用户
     */
    List<User> findByWhere(User user);

    /**
     * 根据姓名模糊以及性别精准查询用户信息
     * 若姓名为null,则按性别精准查询
     * 若性别为null,则按姓名模糊查询
     * 若姓名和性别均不为null,则按姓名模糊且按性别精准查询
     * 若姓名和性别均为null,全查
     *
     * @param user 用户实体
     * @return 满足条件的用户
     */
    List<User> findByTrim(User user);

    /**
     * 根据姓名模糊以及性别精准查询用户信息
     * id不为null时
     * 若姓名不为null,按姓名模糊查询
     * 若姓名为null,但性别不为null,则按性别精准查询
     * 若姓名和性别都为null,则按照id精准查询
     *
     * @param user 用户实体
     * @return 满足条件的用户
     */
    List<User> findByChoose(User user);

  • tst: c.y.dynamic.UserTest.findByIf()
package com.yap.dynamic;

import com.yap.mapper.UserMapper;
import com.yap.pojo.User;
import com.yap.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author yap
 */
public class UserTest {

    private SqlSessionFactory factory = MyBatisUtil.getFactory("mybatis-dynamic.xml");

    @Test
    public void findByIf() {
        try (SqlSession session = factory.openSession()) {
            UserMapper userMapper = session.getMapper(UserMapper.class);
            System.out.println(userMapper.findByIf(new User(null, null, 1, null, null)));
            System.out.println(userMapper.findByIf(new User(null, "王", null, null, null)));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • tst: c.y.dynamic.UserTest.findByWhere()
    @Test
    public void findByWhere() {
        try (SqlSession session = factory.openSession()) {
            UserMapper userMapper = session.getMapper(UserMapper.class);
            System.out.println(userMapper.findByWhere(new User(null, null, null, null, null)));
            System.out.println(userMapper.findByWhere(new User(null, null, 1, null, null)));
            System.out.println(userMapper.findByWhere(new User(null, "王", null, null, null)));
            System.out.println(userMapper.findByWhere(new User(null, "王", 1, null, null)));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  • tst: c.y.dynamic.UserTest.findByTrim()
    @Test
    public void findByTrim() {
        try (SqlSession session = factory.openSession()) {
            UserMapper userMapper = session.getMapper(UserMapper.class);
            System.out.println(userMapper.findByTrim(new User(null, null, null, null, null)));
            System.out.println(userMapper.findByTrim(new User(null, null, 1, null, null)));
            System.out.println(userMapper.findByTrim(new User(null, "王", null, null, null)));
            System.out.println(userMapper.findByTrim(new User(null, "王", 1, null, null)));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  • tst: c.y.dynamic.UserTest.findByChoose()
    @Test
    public void findByChoose() {
        try (SqlSession session = factory.openSession()) {
            UserMapper userMapper = session.getMapper(UserMapper.class);
            System.out.println(userMapper.findByChoose(new User(1, "王", 1, null, null)));
            System.out.println(userMapper.findByChoose(new User(1, "王", null, null, null)));
            System.out.println(userMapper.findByChoose(new User(1, null, 1, null, null)));
            System.out.println(userMapper.findByChoose(new User(1, null, null, null, null)));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

1.2 动态循环标签

概念: 循环标签 <foreach> 用于循环拼接部分指定的SQL语句:

  • collection:值固定为 arraylist 或一个Map结构(值为数组或列表)的KEY。
  • items:设置循环中间变量。
  • open/close:设置循环开始/结束时拼接的字符串SQL代码。
  • separator:设置拼接符,自动忽略SQL语句中多余的拼接符。

源码: /mybatis3/

  • res: classpath:com/yap/mapper/UserMapper.xml
<select id="findWithArray" resultType="user">
        SELECT `id`, `name`, `gender`, `age`, `info` FROM `user`
        <where>
            <foreach collection="array" item="e" open="id in (" close=")" separator=",">
                ${e}
            </foreach>
        </where>
    </select>

    <select id="findWithList" resultType="user">
        SELECT `id`, `name`, `gender`, `age`, `info` FROM `user`
        <where>
            <foreach collection="list" item="e" open="id in (" close=")" separator=",">
                ${e}
            </foreach>
        </where>
    </select>

    <select id="findWithMap" resultType="user">
        SELECT `id`, `name`, `gender`, `age`, `info` FROM `user`
        <where>
            <foreach collection="ids" item="e" open="id in (" close=")" separator=",">
                ${e}
            </foreach>
        </where>
    </select>
  • src: c.y.mapper.UserMapper
  /**
     * 找到编号为3和4和6的用户
     *
     * @param ids 编号数组
     * @return 满足条件的用户
     */
    List<User> findWithArray(int[] ids);

    /**
     * 找到编号为3和4和6的用户
     *
     * @param ids 编号列表
     * @return 满足条件的用户
     */
    List<User> findWithList(List<Integer> ids);

    /**
     * 找到编号为3和4和6的用户
     *
     * @param userMap 用户信息集合
     * @return 满足条件的用户
     */
    List<User> findWithMap(Map<String, Object> userMap);

  • tst: c.y.dynamic.UserTest.findWithArray()
    @Test
    public void findWithArray() {
        int[] ids = {3, 4, 6};
        try (SqlSession session = factory.openSession()) {
            UserMapper userMapper = session.getMapper(UserMapper.class);
            System.out.println(userMapper.findWithArray(ids));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  • tst: c.y.dynamic.UserTest.findWithList()
    @Test
    public void findWithList() {
        List<Integer> ids = new ArrayList<>();
        ids.add(3);
        ids.add(4);
        ids.add(6);
        try (SqlSession session = factory.openSession()) {
            UserMapper userMapper = session.getMapper(UserMapper.class);
            System.out.println(userMapper.findWithList(ids));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  • tst: c.y.dynamic.UserTest.findWithMap()
    @Test
    public void findWithMap() {
        List<Integer> ids = new ArrayList<>();
        ids.add(3);
        ids.add(4);
        ids.add(6);

        Map<String, Object> map = new HashMap<>(1);
        map.put("ids", ids);
        try (SqlSession session = factory.openSession()) {
            UserMapper userMapper = session.getMapper(UserMapper.class);
            System.out.println(userMapper.findWithMap(map));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

1.3 动态修改标签

概念: 修改标签 <set> 用于控制修改操作时,忽略null字段:

  • <set>:生成 SET 关键字并删除SET语句中最后一个 ,
  • <trim prefix="SET" suffixOverrides=",">:生成 SET 前缀关键字并删除SET语句中最后一个 ,

源码: /mybatis3/

  • res: classpath:com/yap/mapper/UserMapper.xml
 <update id="updateBySet">
        UPDATE `user`
        <set>
            <if test='name != null'>`name` = #{name},</if>
            <if test='age != null'>`age` = #{age},</if>
            <if test='gender != null'>`gender` = #{gender},</if>
        </set>
        <where>
            id = #{id}
        </where>
    </update>

    <update id="updateByTrim">
        UPDATE `user`
        <trim prefix="SET" suffixOverrides=",">
            <if test='name != null'>`name` = #{name},</if>
            <if test='age != null'>`age` = #{age},</if>
            <if test='gender != null'>`gender` = #{gender},</if>
        </trim>
        <where>
            id = #{id}
        </where>
    </update>
  • src: c.y.mapper.UserMapper
 /**
     * 根据主键修改用户信息
     *
     * @param user 用户实体
     */
    void updateBySet(User user);

    /**
     * 根据主键修改用户信息
     *
     * @param user 用户实体
     */
    void updateByTrim(User user);
  • tst: c.y.dynamic.UserTest.updateBySet()
  @Test
    public void updateBySet() {
        SqlSession session = factory.openSession();
        try {
            UserMapper userMapper = session.getMapper(UserMapper.class);
            userMapper.updateBySet(new User(1, "赵四", null, null, null));
            userMapper.updateBySet(new User(1, null, null, 100, null));
            session.commit();
        } catch (Exception e) {
            session.rollback();
            e.printStackTrace();
        } finally {
            session.close();
        }
    }
  • tst: c.y.dynamic.UserTest.updateByTrim()
 @Test
    public void updateByTrim() {
        SqlSession session = factory.openSession();
        try {
            UserMapper userMapper = session.getMapper(UserMapper.class);
            userMapper.updateByTrim(new User(1, "刘能", null, null, null));
            userMapper.updateByTrim(new User(1, null, null, 0, null));
            session.commit();
        } catch (Exception e) {
            session.rollback();
            e.printStackTrace();
        } finally {
            session.close();
        }
    }

1.4 多参数接口

概念:

  • @Param:用于对接口参数起别名,仅在客户通过接口方法调用语句块时生效:
    • OGNL表达式和占位符中仅支持别名或 param123..
  • _parameter:SQL语句块中的内置参数,可以在OGNL表达式中直接使用:
    • 接口单个简单参数时,_parameter 即为该参数,使用 #{param1} 获取。
    • 接口多个简单参数时,_parameter.get("param1") 即为第一个参数,使用 #{param1} 获取。
    • 接口单个实体类参数时,直接使用实体类中的属性进行判断和获取。
    • 接口多个实体类参数时,_parameter.param1.name 即为第一个实体类的name属性,使用 #{param1.name} 获取。
    • OGNL表达式和占位符中的 param12.. 均可使用 @Param 设置的别名替代,但不建议。

源码: /mybatis3/

  • res: classpath:com/yap/mapper/UserMapper.xml
<select id="findByNameAndGender" resultType="user">
        SELECT `id`, `name`, `gender`, `age`, `info` FROM `user`
        <where>
            <if test='param1 != null'>
                AND `name` like concat('%', #{param1}, '%')
            </if>
            <if test='param2 != null'>
                AND `gender` = #{param2}
            </if>
        </where>
    </select>

    <select id="findBySingleParam" resultType="user">
        SELECT `id`, `name`, `gender`, `age`, `info` FROM `user`
        <where>
            <if test='_parameter != null'>
                AND `name` like concat('%', #{param1}, '%')
            </if>
        </where>
    </select>

    <select id="findByMultipleParam" resultType="user">
        SELECT `id`, `name`, `gender`, `age`, `info` FROM `user`
        <where>
            <if test='_parameter.get("param1") != null'>
                AND `name` like concat('%', #{param1}, '%')
            </if>
            <if test='_parameter.get("param2") != null'>
                AND `gender` = #{param2}
            </if>
        </where>
    </select>

    <select id="findByPojoParam" resultType="user">
        SELECT `id`, `name`, `gender`, `age`, `info` FROM `user`
        <where>
            <if test='_parameter.param1 != null'>
                <if test='_parameter.param1.name != null'>
                    AND `name` like concat('%', #{param1.name}, '%')
                </if>
                <if test='_parameter.param2.gender != null'>
                    AND `gender` = #{param2.gender}
                </if>
            </if>
        </where>
    </select>

  • src: c.y.mapper.UserMapper
  /**
     * 根据姓名模糊以及性别精准查询用户信息
     *
     * @param name   用户姓名
     * @param gender 用户性别
     * @return 满足条件的用户
     */
    List<User> findByNameAndGender(@Param("aa") String name, @Param("bb") int gender);

    /**
     * 根据姓名模糊查询用户信息
     *
     * @param name 姓名
     * @return 符合条件的用户信息
     */
    List<User> findBySingleParam(@Param("aa") String name);

    /**
     * 根据姓名模糊和性别精准查询用户信息
     *
     * @param name   姓名
     * @param gender 性别
     * @return 符合条件的学生信息
     */
    List<User> findByMultipleParam(@Param("aa") String name, @Param("bb") int gender);

    /**
     * 根据姓名模糊和性别精准查询用户信息
     *
     * @param userA 用户实体
     * @param userB 用户实体
     * @return 符合条件的学生信息
     */
    List<User> findByPojoParam(@Param("aa") User userA, @Param("bb") User userB);

  • tst: c.y.dynamic.UserTest.findByNameAndGender()
    @Test
    public void findByNameAndGender() {
        try (SqlSession session = factory.openSession()) {
            UserMapper userMapper = session.getMapper(UserMapper.class);
            System.out.println(userMapper.findByNameAndGender("王", 1));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  • tst: c.y.dynamic.UserTest.findBySingleParam()
    @Test
    public void findBySingleParam() {
        try (SqlSession session = factory.openSession()) {
            UserMapper userMapper = session.getMapper(UserMapper.class);
            System.out.println(userMapper.findBySingleParam("王"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  • tst: c.y.dynamic.UserTest.findByMultipleParam()
    @Test
    public void findByMultipleParam() {
        try (SqlSession session = factory.openSession()) {
            UserMapper userMapper = session.getMapper(UserMapper.class);
            System.out.println(userMapper.findByMultipleParam("王", 0));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  • tst: c.y.dynamic.UserTest.findByPojoParam()
    @Test
    public void findByPojoParam() {
        User userA = new User(null, "王", null, null, null);
        User userB = new User(null, null, 0, null, null);
        try (SqlSession session = factory.openSession()) {
            UserMapper userMapper = session.getMapper(UserMapper.class);
            System.out.println(userMapper.findByMultiplePojoParam(userA, userB));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

2. 多表查询

概念: mybatis多表间支持一对一和一对多两种关系:

  • 从员工看部门,是一对一关系,一个员工只能指定一个部门。
  • 从部门看员工,是一对多关系,一个部门可以指定多个员工。

源码: /mybatis3/

  • res:练习sql
CREATE TABLE IF NOT EXISTS `dept`
(
    `deptno` INT(2)      NOT NULL PRIMARY KEY,
    `dname`  VARCHAR(14) NULL COMMENT '部门名',
    `loc`    VARCHAR(13) NULL COMMENT '部门地址'
)
    COMMENT '部门表';

INSERT INTO mybatis.`dept`
VALUES (10, 'ACCOUNTING', 'NEW YORK'),
       (20, 'RESEARCH', 'DALLAS'),
       (30, 'SALES', 'CHICAGO'),
       (40, 'OPERATIONS', 'BOSTON');
COMMIT;

CREATE TABLE IF NOT EXISTS `emp`
(
    `empno`    INT(4)      NOT NULL PRIMARY KEY,
    `ename`    VARCHAR(10) NULL COMMENT '员工姓名',
    `job`      VARCHAR(9)  NULL COMMENT '员工工作',
    `mgr`      INT(4)      NULL COMMENT '上级领导编号',
    `hiredate` DATE        NULL COMMENT '入职日期',
    `sal`      INT(7)      NULL COMMENT '员工月薪',
    `comm`     INT(7)      NULL COMMENT '员工补助',
    `deptno`   INT(2)      NULL COMMENT '部门表外键',
    CONSTRAINT FK_EMP_DEPT
        FOREIGN KEY (`deptno`) REFERENCES `dept` (`deptno`)
)
    COMMENT '员工表';

INSERT INTO mybatis.`emp`
VALUES (7369, 'SMITH', 'CLERK', 7902, '1980-12-17', 800, null, 20),
       (7499, 'ALLEN', 'SALESMAN', 7698, '1981-02-20', 1600, 300, 30),
       (7521, 'WARD', 'SALESMAN', 7698, '1981-02-22', 1250, 500, 30),
       (7566, 'JONES', 'MANAGER', 7839, '1981-04-02', 2975, null, 20),
       (7654, 'MARTIN', 'SALESMAN', 7698, '1981-09-28', 1250, 1400, 30),
       (7698, 'BLAKE', 'MANAGER', 7839, '1981-05-01', 2850, null, 30),
       (7782, 'CLARK', 'MANAGER', 7839, '1981-06-09', 2450, null, 10),
       (7788, 'SCOTT', 'ANALYST', 7566, '1987-04-19', 3000, null, 20),
       (7839, 'KING', 'PRESIDENT', null, '1981-11-17', 5000, null, 10),
       (7844, 'TURNER', 'SALESMAN', 7698, '1981-09-08', 1500, 0, 30),
       (7876, 'ADAMS', 'CLERK', 7788, '1987-05-23', 1100, null, 20),
       (7900, 'JAMES', 'CLERK', 7698, '1981-12-03', 950, null, 30),
       (7902, 'FORD', 'ANALYST', 7566, '1981-12-03', 3000, null, 20),
       (7934, 'MILLER', 'CLERK', 7782, '1982-01-23', 1300, null, 10);
COMMIT;
  • res: classpath:mybatis-join.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 resource="jdbc/db.properties"/>
		<!--延迟加载 :分步查询时解决一次主查询伴随N次子查询的N+1问题:  -->
    <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>

    <typeAliases>
        <package name="com.yap.pojo"/>
    </typeAliases>

    <environments default="development">

        <environment id="development">
            <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>

    </environments>

    <mappers>
        <package name="com/yap/mapper"/>
    </mappers>

</configuration>
  • src: c.y.pojo.Dept
package com.yap.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.List;

/**
 * @author yap
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Dept implements Serializable {
   private Integer deptno;
   private String dname;
   private String loc;
   private List<Emp> emps;
}
  • src: c.y.pojo.Emp
package com.yap.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.Date;

/**
 * @author yap
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Emp implements Serializable {
    private Integer empno;
    private String ename;
    private String job;
    private Integer sal;
    private Integer comm;
    private Integer mgr;
    private Date hiredate;
    private Dept deptno;
}

2.1 一对一联表

概念: 在员工实体中埋一个部门实体属性:

  • JOIN查询:SQL块中使用 JOIN,返回值使用 <resultMap> 映射:
    • 使用 <association>javaType/property 映射实体类外键属性类型/属性名。
    • 使用 <association>jdbcType/column 映射数据库外键字段类型/字段名。
    • 使用 <id>/<result> 映射 <resultMap>/<association> 中对应实体的全部非外键属性。
    • 配置对应接口方法并测试。
  • 分步查询:SQL块中使用全查(1步SQL块),返回值使用 <resultMap> 映射:
    • 分步查询时可省略所有同名的非外键属性的映射。
    • 在部门SQL配置文件中开发按主键查部门的2步SQL块,可直接用1步SQL块的外键。
    • 额外使用 <association select="工作空间.id"> 调用2步SQL块。
    • 配置对应接口方法并测试,其中2步SQL块无需配置对应接口方法。
  • 使用注解:可简化分步查询的配置,省略所有SQL块,但SQL配置文件仍需保留:
    • 使用 @Results/@Result/@Result(id=true) 替代 <resultMap>/<result>/<id>
    • 使用 @Result(one=@One("")) 替代 <association select="">
  • 延迟加载:分步查询时可在 <settings> 配置懒加载以解决一次主查询附带N次子查询的N+1问题:
    • <setting name="lazyLoadingEnabled" value="true"/>:开启延迟加载。
    • <setting name="aggressiveLazyLoading" value="false"/>: 关闭积极加载。

1对1 JOIN查询

源码: /mybatis3/

  • src: c.y.mapper.EmpMapper
package com.yap.mapper;

/**
 * @author yap
 */
public interface EmpMapper {

    /**
     * 查询所有员工,同时查出员工所在部门信息
     *
     * @return 所有员工及其部门信息
     */
    List<Emp> findWithDeptByJoin();
}
  • res: classpath:com/yap/mapper/EmpMapper.xml
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.yap.mapper.EmpMapper">
    <cache readOnly="true"/>

    <resultMap id="emp-dept-join" type="emp">
        <id column="empno" property="empno"/>
        <result column="ename" property="ename"/>
        <result column="job" property="job"/>
        <result column="sal" property="sal"/>
        <result column="comm" property="comm"/>
        <result column="mgr" property="mgr"/>
        <result column="hiredate" property="hiredate"/>
        <!--  Emp 需要 private Dept deptno;-->
        <association javaType="dept" property="deptno" column="deptno" jdbcType="INTEGER">
            <id column="deptno" property="deptno"/>
            <result column="dname" property="dname"/>
            <result column="loc" property="loc"/>
        </association>
    </resultMap>

    <select id="findWithDeptByJoin" resultMap="emp-dept-join">
        SELECT e.`empno`,
               e.`ename`,
               e.`job`,
               e.`mgr`,
               e.`hiredate`,
               e.`sal`,
               e.`comm`,
               d.`deptno`,
               d.`dname`,
               d.`loc`
        FROM `emp` e
                 JOIN `dept` d ON e.`deptno` = d.`deptno`
    </select>
</mapper>
  • tst: c.y.join.EmpTest.findWithDeptByJoin()
package com.yap.join;

/**
 * @author yap
 */
public class EmpTest {

    private SqlSessionFactory factory = MyBatisUtil.getFactory("mybatis-join.xml");

    @Test
    public void findWithDeptByJoin() {
        try (SqlSession session = factory.openSession()) {
            EmpMapper empMapper = session.getMapper(EmpMapper.class);
            for (Emp emp : empMapper.findWithDeptByJoin()) {
                System.out.printf("%s 在 %s 部门\n", emp.getEname(), emp.getDeptno().getDname());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

1对1 分步查询

  • src: c.y.mapper.EmpMapper
    /**
     * 查询所有员工,同时查出员工所在部门信息
     *
     * @return 所有员工及其部门信息
     */
    List<Emp> findWithDeptBySelect();

  • res: classpath:com/yap/mapper/EmpMapper.xml
    <resultMap id="emp-dept-select" type="emp">
        <association javaType="dept" property="deptno" column="deptno" jdbcType="INTEGER"
                     select="com.yap.mapper.DeptMapper.findByDeptno"/>
    </resultMap>

    <select id="findWithDeptBySelect" resultMap="emp-dept-select">
        SELECT e.`empno`,
               e.`ename`,
               e.`job`,
               e.`mgr`,
               e.`hiredate`,
               e.`sal`,
               e.`comm`,
               e.`deptno`
        FROM `emp` e
    </select>
  • res: classpath:com/yap/mapper/DeptMapper.xml
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.yap.mapper.DeptMapper">
    <cache readOnly="true"/>

    <select id="findByDeptno" resultType="dept">
        SELECT `deptno`, `dname`, `loc` FROM `dept`
        <where>
            <if test='_parameter != null'>
                `deptno` = #{param1}
            </if>
        </where>
    </select>
</mapper>
  • tst: c.y.join.EmpTest.findWithDeptBySelect()
    @Test
    public void findWithDeptBySelect() {
        try (SqlSession session = factory.openSession()) {
            EmpMapper empMapper = session.getMapper(EmpMapper.class);
            for (Emp emp : empMapper.findWithDeptBySelect()) {
                System.out.printf("%s 在 %s 部门\n", emp.getEname(), emp.getDeptno().getDname());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

1对1 使用注解

  • src: c.y.mapper.EmpMapper
    /**
     * 查询所有员工,同时查出员工所在部门信息
     *
     * @return 所有员工及其部门信息
     */
    @Select("SELECT `empno`, `ename`, `job`, `mgr`, `hiredate`, `sal`, `comm`, `deptno` FROM `emp`")
    @Results(id = "emp-dept-one", value = {
            // empno可以不用配置
            @Result(column = "empno", property = "empno", jdbcType = JdbcType.INTEGER, id = true),
            @Result(column = "deptno", property = "deptno", javaType = Dept.class,
                    one = @One(select = "com.yap.mapper.DeptMapper.findByDeptno")
            )
    })
    List<Emp> findWithDeptByOne();

  • res: classpath:com/yap/mapper/DeptMapper.xml
<select id="findByDeptno" resultType="dept">
        SELECT `deptno`, `dname`, `loc` FROM `dept`
        <where>
            <if test='_parameter != null'>
                `deptno` = #{param1}
            </if>
        </where>
    </select>
  • tst: c.y.join.EmpTest.findWithDeptByOne()
    @Test
    public void findWithDeptByOne() {
        try (SqlSession session = factory.openSession()) {
            EmpMapper empMapper = session.getMapper(EmpMapper.class);
            for (Emp emp : empMapper.findWithDeptByOne()) {
                System.out.printf("%s 在 %s 部门\n", emp.getEname(), emp.getDeptno().getDname());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

1对1 延迟加载

  • res: classpath:mybatis-join.xml
    <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
  • tst: c.y.join.EmpTest.findWithDeptByLazy()
    @Test
    public void findWithDeptByLazy() {
        try (SqlSession session = factory.openSession()) {
            EmpMapper empMapper = session.getMapper(EmpMapper.class);
            // debug here
            List<Emp> emps = empMapper.findWithDeptBySelect();
            System.out.println(emps.get(0).getEname());
            System.out.println(emps.get(0).getSal());
            System.out.println(emps.get(0).getDeptno().getDname());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

2.2 一对多联表

概念: 在部门实体中埋一个员工实体列表属性:

  • JOIN查询:SQL块中使用 JOIN,返回值使用 <resultMap> 映射:
    • 使用 <collection>ofType/property 映射实体类外键属性泛型类型/属性名。
    • 使用 <collection>jdbcType/column 映射数据库外键字段类型/字段名。
    • 使用 <id>/<result> 映射 <resultMap>/<collection> 中对应实体的全部非外键属性。
    • 配置对应接口方法并测试。
  • 分步查询:SQL块中使用全查(1步SQL块),返回值使用 <resultMap> 映射:
    • 分步查询时可省略所有同名的非外键属性的映射。
    • 在部门SQL配置文件中开发按主键查部门的2步SQL块,可直接用1步SQL块的外键。
    • 额外使用 <collection select="工作空间.id"> 调用2步SQL块。
    • 配置对应接口方法并测试,其中2步SQL块无需配置对应接口方法。
  • 使用注解:可简化分步查询的配置,省略所有SQL块,但SQL配置文件仍需保留:
    • 使用 @Results/@Result/@Result(id=true) 替代 <resultMap>/<result>/<id>
    • 使用 @Result(many=@Many("")) 替代 <collection select="">
  • 延迟加载:分步查询时可配置 <settings> 以解决一次主查询伴随N次子查询的N+1问题:
    • <setting name="lazyLoadingEnabled" value="true"/>:开启延迟加载。
    • <setting name="aggressiveLazyLoading" value="false"/>: 关闭积极加载。

1对多 JOIN查询

源码: /mybatis3/

  • src: c.y.mapper.DeptMapper
package com.yap.mapper;
/**
 * @author yap
 */
public interface DeptMapper {

    /**
     * 查询所有部门,同时查出部门所有员工信息
     *
     * @return 所有部门及其员工信息
     */
    List<Dept> findWithEmpsByJoin();
}
  • res: classpath:com/yap/mapper/DeptMapper.xml
    <resultMap id="dept-emp-join" type="dept">
        <id property="deptno" column="deptno"/>
        <result property="dname" column="dname"/>
        <result property="loc" column="loc"/>
        <!--    private List<Emp> emps; 在pojo中添加-->
        <collection ofType="emp" property="emps" column="deptno" jdbcType="INTEGER">
            <id property="empno" column="empno"/>
            <result property="ename" column="ename"/>
            <result property="job" column="job"/>
            <result property="sal" column="sal"/>
            <result property="comm" column="comm"/>
            <result property="mgr" column="mgr"/>
            <result property="hiredate" column="hiredate"/>
        </collection>
    </resultMap>

    <select id="findWithEmpsByJoin" resultMap="dept-emp-join">
        SELECT e.`empno`,
               e.`ename`,
               e.`job`,
               e.`mgr`,
               e.`hiredate`,
               e.`sal`,
               e.`comm`,
               d.`deptno`,
               d.`dname`,
               d.`loc`
        FROM `dept` d
                 LEFT JOIN `emp` e ON e.`deptno` = d.`deptno`
    </select>

  • tst: c.y.join.DeptTest.findWithEmpsByJoin()
package com.yap.join;
/**
 * @author yap
 */
public class DeptTest {

    private SqlSessionFactory factory = MyBatisUtil.getFactory("mybatis-join.xml");

    @Test
    public void findWithEmpsByJoin() {
        try (SqlSession session = factory.openSession()) {
            DeptMapper deptMapper = session.getMapper(DeptMapper.class);
            for (Dept dept : deptMapper.findWithEmpsByJoin()) {
                for (Emp emp : dept.getEmps()) {
                    System.out.print(emp.getEname() + " ");
                }
                System.out.println("在" + dept.getDname() + "部门");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

1对多 分步查询

源码: /mybatis3/

  • src: c.y.mapper.DeptMapper
    /**
     * 查询所有部门,同时查出部门所有员工信息
     *
     * @return 所有部门及其员工信息
     */
    List<Dept> findWithEmpsBySelect();
  • res: classpath:com/yap/mapper/DeptMapper.xml
    <resultMap id="dept-emp-select" type="dept">
        <collection  property="emps" ofType="emp" column="deptno" jdbcType="INTEGER"
                     select="com.yap.mapper.EmpMapper.findByDeptno"/>
    </resultMap>

    <select id="findWithEmpsBySelect" resultMap="dept-emp-select">
        SELECT d.`deptno`, d.`dname`, d.`loc`
        FROM `dept` d
    </select>
  • res: classpath:com/yap/mapper/EmpMapper.xml
    <select id="findByDeptno" resultType="emp">
        SELECT e.`empno`, e.`ename`, e.`job`, e.`mgr`, e.`hiredate`, e.`sal`, e.`comm`, e.`deptno`
        FROM `emp` e
        <where>
            <if test='_parameter != null'>
                `deptno` = #{param1}
            </if>
        </where>
    </select>
  • tst: c.y.join.DeptTest.findWithEmpsBySelect()
    @Test
    public void findWithEmpsBySelect() {
        try (SqlSession session = factory.openSession()) {
            DeptMapper deptMapper = session.getMapper(DeptMapper.class);
            for (Dept dept : deptMapper.findWithEmpsBySelect()) {
                for (Emp emp : dept.getEmps()) {
                    System.out.print(emp.getEname() + " ");
                }
                System.out.println("在" + dept.getDname() + "部门");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

1对多 使用注解

源码: /mybatis3/

  • src: c.y.mapper.DeptMapper
    /**
     * 查询所有部门,同时查出部门所有员工信息
     *
     * @return 所有部门及其员工信息
     */
    @Select("SELECT `deptno`, `dname`, `loc` FROM `dept`")
    @Results(id = "dept-emp-many", value = {
            @Result(column = "deptno", property = "emps",
                    many = @Many(select = "com.joezhou.mapper.EmpMapper.findByDeptno")
            )
    })
    List<Dept> findWithEmpsByMany();
  • res: classpath:com/yap/mapper/EmpMapper.xml
    <select id="findByDeptno" resultType="emp">
        SELECT e.`empno`, e.`ename`, e.`job`, e.`mgr`, e.`hiredate`, e.`sal`, e.`comm`, e.`deptno`
        FROM `emp` e
        <where>
            <if test='_parameter != null'>
                `deptno` = #{param1}
            </if>
        </where>
    </select>
  • tst: c.y.join.DeptTest.findWithEmpsByMany()

    @Test
    public void findWithDeptByMany() {
        try (SqlSession session = factory.openSession()) {
            DeptMapper deptMapper = session.getMapper(DeptMapper.class);
            for (Dept dept : deptMapper.findWithEmpsByMany()) {
                for (Emp emp : dept.getEmps()) {
                    System.out.print(emp.getEname() + " ");
                }
                System.out.println("在" + dept.getDname() + "部门");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

1对多 延迟加载

源码: /mybatis3/

  • res: classpath:mybatis-join.xml
    <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
  • tst: c.y.join.DeptTest.findWithEmpsByLazy()
    @Test
    public void findWithEmpsByLazy() {
        try (SqlSession session = factory.openSession()) {
            DeptMapper deptMapper = session.getMapper(DeptMapper.class);
            // debug here
            List<Dept> depts = deptMapper.findWithEmpsBySelect();
            System.out.println(depts.get(0).getDname());
            System.out.println(depts.get(0).getLoc());
            System.out.println(depts.get(0).getEmps().get(0).getEname());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }