Loading

19-MyBatis(1)

Mybatis 概述

Mybatis 是 Apache 的一个开源项目 iBatis,iBatis 3.x 正式更名为 MyBatis。
iBatis 一词来源于 "internet" 和 "abatis" 的组合,是一个基于 Java 的持久层框架。iBatis 提供的持久层框架包括 SQL Maps 和 Data Access Object(DAO)。

  • MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架
  • MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
  • MyBatis 可以使用简单的 XML 或注解用于配置和原始映射,将接口和 Java 的 POJO(Plain Old Java Objects,普通的 Java 对象) 映射成数据库中的记录
  • Mybatis 是一个半自动的 ORM(Object Relation Mapping) 框架

现有持久化技术的对比

  • JDBC
    • SQL 夹在 Java 代码块里,耦合度高导致硬编码内伤
    • 维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见
  • Hibernate 和 JPA
    • 长难复杂 SQL,对于 Hibernate 而言处理也不容易
    • 内部自动生产的 SQL,不容易做特殊优化
    • 基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难,导致 DB 性能下降
  • MyBatis
    • 对开发人员而言,核心 SQL 还是需要自己优化
    • SQL 和 Java 编码分开,功能边界清晰,一个专注业务,一个专注数据

HelloWorld

  1. 导入 jar
  2. 配置 log4j.xml // 定义日志输出格式
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
    <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
        <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
        <param name="Encoding" value="UTF-8" />
            <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern"
                    value="%-5p %d{MM-dd HH:mm:ss,SSS} %m  (%F:%L) \n" />
            </layout>
        </appender>
        <logger name="java.sql">
            <level value="debug" />
        </logger>
        <logger name="org.apache.ibatis">
            <level value="info" />
        </logger>
        <root>
            <level value="debug" />
            <appender-ref ref="STDOUT" />
        </root>
    </log4j:configuration>
    
  3. 创建 jdbc.properties
    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql:///test
    jdbc.username=root
    jdbc.password=root
    
  4. 创建 JavaBean 以及 DB 对应表
  5. 创建 Mybatis 的核心(全局) 配置文件 [mybatis-config.xml] // 如何连接 DB
    <?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.properties"></properties>
    
        <settings>
            <!-- 下划线 → 驼峰 -->
            <setting name="mapUnderscoreToCamelCase" value="true"/>
        </settings>
    
        <!-- 设置连接 DB 的环境(可设置多个) -->
        <environments default="MySQL">
            <!-- 设置某个具体的 DB 环境 -->
            <environment id="MySQL">
                <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>
            <mapper resource="UserMapper.xml" />
        </mappers>
    
    </configuration>
    
  6. 创建 Mapper 接口,在 mapper.xml 中实现 2 个绑定
    public interface UserMapper {
        User getUserByUid(String uid);
    }
    
  7. 创建映射文件 [XxxMapper.xml] // 如何操作 DB
    <?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">
    
        <!-- 1. [接口全限定名] 和 [映射文件根标签的命名空间] 一致(绑定) -->
        <mapper namespace="cn.edu.nuist.mapper.UserMapper">
        <!-- 2. [方法名] 和 [SQL 语句标签的 id 属性值] 一致(绑定) -->
        <select id="getUserByUid" resultType="cn.edu.nuist.bean.User">
            <!-- 如果没有配置 mapUnderscoreToCamelCase,就给 user_name 起个别名 -->
            SELECT uid, user_name, password, address, phone FROM user WHERE uid = #{id}
        </select>
    
        <!--
            select * from user where uid = #{id}
                (PStat) ==> select * from user where uid = ?
            select * from user where uid = ${value}
                (Stat) ==> select * from user where uid = 1
         -->
    </mapper>
    
  8. 获取 Mybatis 操作数据库的会话对象(SqlSession) 并进行测试
    @Test
    public void test() throws IOException {
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession = factory.openSession();
        // getMapper(): 会通过动态代理生成 UserMapper 的代理实现类
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // com.sun.proxy.$Proxy4
        System.out.println(mapper.getClass().getName());
        User user = mapper.getUserByUid("1");
        System.out.println(user);
    }
    ====================== 测试结果 ======================
    DEBUG 08-16 15:23:32,125 ==> Preparing: select uid, user_name, password
            , address, phone from user where uid = ?  (BaseJdbcLogger.java:145)
    DEBUG 08-16 15:23:32,165 ==> Parameters: 1(String)  (BaseJdbcLogger.java:145)
    DEBUG 08-16 15:23:32,481 <== Total: 1  (BaseJdbcLogger.java:145)
    User [uid=1, userName=刘心悠, password=xiaohai, address=HongKong, phone=123456789]
    

Tip:没有网络时 XML 的提示功能

全局配置文件

The MyBatis configuration contains settings and properties that have a dramatic effect on how MyBatis behaves。

environments 环境配置

  • MyBatis 可以配置多种环境,比如开发、测试和生产环境需要有不同的配置
  • 每种环境使用一个 <environment> 标签进行配置并指定唯一标识符
    • 可以通过 <environments> 标签中的 default 属性指定一个环境的标识符来快速的切换环境
    • <environment> 指定具体环境,其中 id 属性指定当前环境的唯一标识,<transactionManager><dataSource> 子标签都必须有
  • <transactionManager> 标签设置事务;type 属性取值常见的有如下几种
    • JDBC:使用原生 JDBC 的提交和回滚设置
    • MANAGED:让容器来管理事务的整个生命周期
    • POOLED:使用数据库连接池
    • UNPOOLED:不使用数据库连接池
    • 自定义:实现 TransactionFactory<I>,type=全类名/别名
  • 实际开发中我们使用 Spring 管理数据源,并进行事务控制的配置来覆盖上述配置
<environments default="oracle">
    <environment id="mysql">
        <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>

    <environment id="oracle">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="${orcl.driver}" />
            <property name="url" value="${orcl.url}" />
            <property name="username" value="${orcl.username}" />
            <property name="password" value="${orcl.password}" />
        </dataSource>
    </environment>
</environments>

properties 属性

  • 可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来配置
    <properties>
        <property name="driver" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql:///test" />
        <property name="username" value="root" />
        <property name="password" value="1234" />
    </properties>
    
  • properties 的作用并不单单是这样,你可以创建一个资源文件,名为 jdbc.properties 的文件,将 4 个连接字符串的数据在资源文件中通过键值对(key=value) 的方式放置,不要任何符号,一条占一行。最终在 <environment> 元素的 <dataSource> 元素动态设置连接 DB 的参数(见 ## 4.1 code)
    >>> [jdbc.properties] <<<
    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql:///test
    jdbc.username=root
    jdbc.password=1234
    
    >>> [mybatis-config.xml] <<<
    <!--
    <properties> 设置或引入资源文件
        resource: 在类路径下访问资源文件
        url: 在网络路径或磁盘路径下访问资源文件
    -->
    <properties resource="jdbc.properties"></properties>
    

settings 设置

MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。包含如下的 setting 设置:

<settings>
    <setting name="cacheEnabled" value="true"/>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="multipleResultSetsEnabled" value="true"/>
    <setting name="useColumnLabel" value="true"/>
    <setting name="useGeneratedKeys" value="false"/>
    <setting name="autoMappingBehavior" value="PARTIAL"/>
    <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
    <setting name="defaultExecutorType" value="SIMPLE"/>
    <setting name="defaultStatementTimeout" value="25"/>
    <setting name="defaultFetchSize" value="100"/>
    <setting name="safeRowBoundsEnabled" value="false"/>
    <setting name="mapUnderscoreToCamelCase" value="false"/>
    <setting name="localCacheScope" value="SESSION"/>
    <setting name="jdbcTypeForNull" value="OTHER"/>
    <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

mapUnderscoreToCamelCase:将下划线映射为驼峰

typeAliases 别名处理

  • 类型别名是为 Java 类型设置一个短的名字,可以方便我们引用某个类
    <typeAliases>
        <!-- 若不设置 alias 属性,则默认的别名就是简单类名 (不区分大小写) -->
        <typeAlias type="cn.edu.nuist.beans.Employee" alias="emp"/>
    </typeAliases>
    
  • 类很多的情况下,可以批量设置别名。这个包下的每一个类创建一个默认的别名,就是简单类名小写
    <typeAliases>
        <package name="cn.edu.nuist.beans"/>
    </typeAliases>
    
  • MyBatis已经取好的别名

mappers 映射器

  • 用来在 Mybatis 初始化的时候,告诉 Mybatis 需要引入哪些 Mapper 映射文件
  • 在核心配置文件的 <mapper> 中逐个注册 SQL 映射文件
    <mappers>
        <!-- resource:引入类路径下的文件 -->
        <mapper resource="EmpMapper.xml" />
        <mapper resource="DeptMapper.xml" />
    </mappers>
    
  • 有 SQL 映射文件,要求 Mapper<I> 与 SQL 映射文件同名同位置;没有 SQL 映射文件,使用注解在接口的方法上写 SQL 语句
  • 可使用 <package> 实现批量注册,这种方式要求 SQL 映射文件名必须和接口名相同并且在同一目录下

映射文件

MyBatis 的真正强大在于它的映射语句,也是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 就是针对 SQL 构建的,并且比普通的方法做的更好。

实现 CRUD

select

  • EmpMapper.java
    Emp getEmpByEid(String eid);
    List<Emp> getAllEmps();
    
  • EmpMapper.xml
    <select id="getEmpByEid" resultType="Emp">
        SELECT * FROM emp WHERE eid = #{eid }
    </select>
    <!--
        这里的 resultType 依旧是表记录所映射的 Java 类型
        Mybatis 底层会自动将获取到的 JavaBean 们放入方法
        返回值所声明的容器类型返回。重要的是映射关系!
     -->
    <select id="getAllEmps" resultType="Emp">
        SELECT * FROM emp
    </select>
    

insert

  • EmpMapper.java
    void addEmp(Emp emp);
    
  • EmpMapper.xml
    <insert id="addEmp">
        INSERT INTO emp VALUES(null, #{ename}, #{age}, #{sex})
    </insert>
    

update

  • EmpMapper.java
    // 受影响的记录行数
    Integer updateEmp(Emp emp);
    
  • EmpMapper.xml
    <update id="updateEmp" parameterType="Emp">
        UPDATE emp SET ename=#{ename}, age=#{age}, sex=#{sex} WHERE eid=#{eid}
    </update>
    

delete

  • EmpMapper.java
    // 是否执行成功
    Boolean deleteEmp(String eid);
    
  • EmpMapper.xml
    <delete id="deleteEmp">
        DELETE FROM emp WHERE eid=#{eid}
    </delete>
    

测试

@Test
public void test() throws IOException {
    InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);

    /* mybatis-config.xml: <transactionManager type="JDBC" /> ↓
    使用 JDBC 原生的事务管理方式,即 commit 和 rollback 都需要手动处理
    SqlSession session = factory.openSession([false]); // 手动处理事务 */

    SqlSession session = factory.openSession(true); // 自动处理事务
    EmpMapper mapper = session.getMapper(EmpMapper.class);

    /* 根据 eid 获取员工信息
    Emp emp = mapper.getEmpByEid("1");
    System.out.println(emp); */

    /* 获取所有的员工信息
    List<Emp> emps = mapper.getAllEmps();
    System.out.println(emps); */

    /* 添加员工信息
    Emp emp = new Emp("外援", 30, "741");
    mapper.addEmp(emp); */
    // 如果没提交,数据库中不会增添记录,但是主键会自增
    // session.commit(); // 提交事务

    /* 删除员工
    boolean success = mapper.deleteEmp("7");
    System.out.println(success); */
}

select 查询的几种情况

  1. 查询单行数据,返回单个对象
    <!--
        Emp getEmpByEid(String eid);
     -->
    <select id="getEmpByEid" resultType="Emp">
        SELECT * FROM emp WHERE eid = ${eid }
    </select>
    
  2. 查询多行数据,返回对象组成的集合
    <!--
    List<Emp> getAllEmps();
        这里 resultType 依旧设置为记录所映射的 Java 类型!
        Mybatis 底层会自动将获取到的 Java 对象们放入方法
        返回值所声明的容器类型返回。
    -->
    <select id="getAllEmps" resultType="Emp">
        SELECT * FROM emp
    </select>
    
  3. 查询单行数据,返回 Map 集合
    <!--
        // Map {attr1:value1, attr2: value2 ...}
        Map<String, Object> getEmpMapByEid(String eid);
     -->
    <select id="getEmpMapByEid" resultType="java.util.HashMap">
        SELECT eid, ename, age, sex FROM emp WHERE eid=#{eid}
    </select>
    
  4. 查询多行数据,返回 Map 集合
    <!--
        @MapKey("eid") // 指定使用对象的哪个属性来充当 map 的 key
        Map<String, Object> getAllEmpMap();
     -->
    <select id="getAllEmpMap" resultType="Emp">
        SELECT eid, ename, age, sex FROM emp
    </select>
    

主键生成方式、获取主键值

Statement.RETURN_GENERATED_KEYS 值且使用的是 INSERT 语句时,可以取出新插入数据行中自动增长的列的值。

PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException;

autoGeneratedKeys 可以取值:
  > Statement.RETURN_GENERATED_KEYS(= 1)
  > Statement.NO_GENERATED_KEYS(= 2)
===============================================
PreparedStatement ps = conn.preparedStatement(sql, Statement.RETURN_GENERATED_KEYS);
ResultSet rsKey = ps.getGeneratedKeys();
rsKey.next();
newId = rsKey.getInt(1);

  • 若数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server),则可以设置 useGeneratedKeys="true",然后再用 keyProperty 来设置自动生成的主键赋值给形参 bean 的哪个属性
    <insert id="insertEmp" databaseId="mysql"
            useGeneratedKeys="true" keyProperty="eid">
        INSERT INTO emp VALUES(null, '${ename}', ${age}, '${sex}')
    </insert>
    
  • 对于不支持自增型主键的数据库(例如 Oracle),则可以使用 selectKey 子元素,它将会首先运行,id 会被设置,然后插入语句会被调用
    <insert id="insertEmp" databaseId="oracle">
        <selectKey order="BEFORE" keyProperty="eid" resultType="integer">
            SELECT emp_seq.nextval from dual
        </selectKey>
        INSERT INTO emp (eid, ename, age, sex) values(#{eid}, #{ename}, #{sex}, #{age})
    </insert>
    

参数传递

参数传递的方式

  • 单个普通类型参数:可以接受基本类型,包装类型,字符串类型等。这种情况MyBatis可直接使用这个参数,不需要经过任何处理
  • 多个参数:任意多个参数,都会被 MyBatis 重新包装成一个 Map 传入。Map 的 key 是 param1,param2,或者0,1,…,value 就是参数的值
  • 命名参数:为参数使用 @Param 起一个名字,MyBatis 就会将这些参数封装进 map 中,key 就是我们自己指定的名字
  • POJO:当这些参数属于我们业务 POJO 时,我们直接传递 POJO
  • Map:也可以封装多个参数为 map,直接传递
  • Collection/Array:会被 MyBatis 封装成一个 map 传入,Collection 对应的 key 是 "collection",Array 对应的 key 是 array,如果是 List 集合,key 还可以是 list

参数获取的 2 种方式

有 2 种,举例说明(建议使用 #{...};但特殊情况下,比如模糊查询和批量删改,需要使用 ${...}

#{...} // PreparedStatement, 可以使用通配符,底层填充参数的时候会自动加上单引号
    INSERT INTO emp VALUES(null, #{ename}, #{age}, #{sex})
    Preparing: INSERT INTO emp VALUES(null, ?, ?, ?)
    Parameters: 警察(String), 35(Integer), 110(String)
    Updates: 1
========================================================================
${...} // Statement, 必须使用字符串拼接的方式操作 SQL,一定要注意单引号问题
    INSERT INTO emp VALUES(null, '${ename}', ${age}, '${sex}')
    >>> Preparing: INSERT INTO emp VALUES(null, 黑帮, 30, 444)
    ·············↑ MySQLSyntaxErrorException ↑·············
    INSERT INTO emp VALUES(null, '${ename}', ${age}, '${sex}')
    >>> INSERT INTO emp VALUES(null, '黑帮', 30, '444') ← 手动加引号
    Parameters:

不同的参数类型,${...} 和 #{...} 的不同取值方式:

  • 当传输参数为单个String 或 基本数据类型和其包装类
    • #{...} 可以以任意的名字获取参数值(只是一个占位符)
    • ${...} 只能以 ${value}${_parameter} 获取
    • 代码演示
      <!-- Emp getEmpByEid(String eid); -->
      <select id="getEmpByEid" resultType="Emp">
          <!-- #{...} 随便写,只作占位符之用,但尽量写方法形参名 -->
          SELECT * FROM emp WHERE eid=#{lalalalalaLisa}
          <!--
              SELECT * FROM emp WHERE eid=${eid}
              >>> There is no getter for property named 'eid' in 'class java.lang.String'
              当使用 ${...} 来取值的时候,默认会把传过来的参数当作一个实体类对象。然后把 ${...}
              中的内容当作该实体类对象的一个属性,并通过 getXxx 方法取得这个属性所对应的值
              >>> 所以!${...} 只能以固定的格式:${value} 或 ${_parameter} 获取参数
          -->
          <!--
          SELECT * FROM emp WHERE eid=${value}
          SELECT * FROM emp WHERE eid=${_parameter}
          -->
      </select>
      
  • 当传输参数为 JavaBean
    • #{...}${...} 都可以通过属性名直接获取属性值
    • 要注意 ${...} 的单引号问题
  • 当传输多个参数时,Mybatis 会默认将这些参数放在 Map 集合中,2 种方式实现
    • key 为 0, 1, 2, ... , N-1; value 为参数值 // 参数索引
    • key 为 param1, param2, ..., paramN; value 为参数值 // 参数个数
    • 代码演示
      <!-- Emp getEmpByEidAndEname(String eid, String ename); -->
      <select id="getEmpByEidAndEname" resultType="Emp">
          <!--
              SELECT * FROM emp WHERE eid=#{eid} AND ename=#{ename}·
              BindingException: Parameter 'eid' not found. Available
              parameters are [0, 1, param1, param2]
          -->
          <!--
              #{...}: #{0}, #{1}; #{param1}, #{param2}
              ${...}: '${param1}', '${param2}' // 不能用参数索引方式
                  SELECT * FROM emp WHERE eid='${0}' AND ename='${1}'
                  → SELECT * FROM emp WHERE eid='0' AND ename='1'
          -->
          <!-- SELECT * FROM emp WHERE eid=#{0} AND ename=#{1} -->
          SELECT * FROM emp WHERE eid='${param1}' AND ename='${param2}'
      </select>
      
  • 当传输 Map 参数时,#{...}${...} 都可以通过 key 的名字直接获取值,但是要注意 ${...} 的单引号问题
    <!-- Emp getEmpByMap(Map<String, Object> paramMap); -->
    <select id="getEmpByMap" resultType="emp">
        SELECT * FROM emp WHERE eid='${eid}' AND ename='${ename}'
    </select>
    ==========
    Map<String, Object> paramMap = new HashMap<>();
    paramMap.put("eid", 1);
    paramMap.put("ename", "Finch");
    Emp emp = mapper.getEmpByMap(paramMap);
    
  • 【命名参数】通过 @Param("key") 为 paramMap 指定 key 的名字
    • 无需手动创建封装参数的Map,既不需要 012 也不需要 param123
    • 代码演示
      <!--
          Emp getEmpByEidAndEnameByParam(
              @Param("eid")String eid, @Param("ename")String ename
          );
       -->
      <select id="getEmpByEidAndEnameByParam" resultType="emp">
          SELECT * FROM emp WHERE eid='${eid}' AND ename='${ename}'
      </select>
      
  • 当传输参数为 List 或数组,Mybatis 会将 List 或数组放在 map 中 List 以 list 为键,数组以 array 为键

源码分析

@Test
public void test5() throws IOException {
    InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
    SqlSession session = factory.openSession(true);
    ParamMapper mapper = session.getMapper(ParamMapper.class);

    // (@Param("eid")String eid, @Param("ename")String ename)
    Emp emp = mapper.getEmpByEidAndEnameByParam("1", "Finch");
    System.out.println(emp);
}

resultType 自动映射

  • MyBatis 中在查询进行 select 映射的时候,返回类型可以用 resultType,也可以用 resultMap。resultType 是直接表示返回类型的,而 resultMap 则是对外部 ResultMap 的引用,但是 resultType 跟 resultMap 不能同时存在。
  • 在 MyBatis 进行查询映射时,其实查询出来的每一个属性都是放在一个对应的 Map 里面的,其中键是属性名,值则是其对应的值
    • 当提供的返回类型属性是 resultType 时,MyBatis 会将 Map 里面的键值对取出赋给 resultType 所指定的对象对应的属性。所以其实 MyBatis 的每一个查询映射的返回类型都是 ResultMap,只是当提供的返回类型属性是 resultType 的时候,MyBatis 对自动的给把对应的值赋给 resultType 所指定对象的属性。
    • 当提供的返回类型是 resultMap 时,因为 Map 不能很好表示领域模型,就需要自己再进一步的把它转化为对应的对象,这常常在复杂查询中很有作用。
  • autoMappingBehavior
    • 默认是 PARTIAL,开启自动映射的功能。唯一的要求是结果集列名和 JavaBean 属性名一致
    • 如果设置为 null 则会取消自动映射
  • 数据库字段命名规范,POJO 属性符合驼峰命名法,如 A_COLUMN ↔ aColumn,我们可以开启自动驼峰命名规则映射功能:mapUnderscoreToCamelCase = true

resultMap & 延迟加载

自定义 resultMap,描述如何将结果集映射到 Java 对象。实现高级结果集映射

基本使用

  • id & result
    • id :用于完成主键值的映射;result :用于完成普通列的映射
    • 常见属性
  • association(一个复杂的类型关联,处理多对一的映射关系)
    • POJO 中的属性可能会是一个对象,我们可以使用联合查询:① 以级联属性的方式封装对象;② 使用 association 标签定义对象的封装规则
    • 代码演示
      <!-- ① 级联属性赋值 -->
      <resultMap type="Emp" id="empMap">
          <id column="eid" property="eid"/>
          <result column="ename" property="ename"/>
          <result column="age" property="age"/>
          <result column="sex" property="sex"/>
          <result column="did" property="dept.did"/>
          <result column="dname" property="dept.dname"/>
      </resultMap>
      
      <!-- ② association 标签 -->
      <resultMap type="Emp" id="empMap">
          <id column="eid" property="eid"/>
          <result column="ename" property="ename"/>
          <result column="age" property="age"/>
          <result column="sex" property="sex"/>
          <association property="dept" javaType="Dept">
              <id column="did" property="did"/>
              <result column="dname" property="dname"/>
          </association>
      </resultMap>
      
      <!-- List<Emp> getAllEmp(); -->
      <select id="getAllEmp" resultType="Emp" resultMap="empMap">
          SELECT e.*, d.dname FROM emp e LEFT JOIN dept d ON e.did = d.did
      </select>
      
  • collection : 复杂类型的集合
    • 处理一对多和多对多的映射关系
    • ofType 属性:指集合中的类型(和 javaType 区分开)
    • 代码演示
      <select id="getDeptIncludeEmpsByDid" resultMap="deptMap">
          SELECT e.*, d.dname FROM emp e RIGHT JOIN dept d ON e.did = d.did
          WHERE d.did = e.did AND e.did = #{eid}
      </select>
      
      <resultMap type="Dept" id="deptMap">
          <id column="did" property="did"/>
          <result column="dname" property="dname"/>
          <collection property="emps" ofType="Emp">
              <id column="eid" property="eid"/>
              <result column="ename" property="ename"/>
              <result column="age" property="age"/>
              <result column="sex" property="sex"/>
          </collection>
      </resultMap>
      

分步查询

实际的开发中,对于每个实体类都应该有具体的增删改查方法,也就是 DAO 层, 因此对于查询员工信息并且将对应的部门信息也查询出来的需求,就可以通过分步的方式完成查询。

举例:① 先通过员工的 id 查询员工信息;② 再通过查询出来的员工信息中的外键(部门 id) 查询对应的部门信息。

<!-- ① Emp getEmpStep(String eid); -->
<select id="getEmpStep" resultMap="empMapStep">
    SELECT * FROM emp WHERE eid = #{eid}
</select>
<!-- ② 分步查询,再根据员工的 did 查询员工所在部门 -->
<resultMap type="Emp" id="empMapStep">
    <id column="eid" property="eid"/>
    <result column="ename" property="ename"/>
    <result column="age" property="age"/>
    <result column="sex" property="sex"/>
    <!--
        select 值是分步 SQL 的 id (namespace.id)
        column 是分步查询的字段名(必须是上一步从 DB 中查出来的)
        property 值所对应的属性保存分步查询的结果
    -->
    <association column="did" property="dept"
            select="cn.edu.nuist.mapper.DeptMapper.getDeptByDid" />
</resultMap>

对于查询部门信息并且将对应的所有的员工信息也查询出来的需求,也可以通过分步的方式完成查询。

举例:① 先通过部门的 id 查询部门信息;② 再通过部门 id 作为员工的外键查询对应的部门信息。

<select id="getOnlyDeptInfoByDid" resultMap="deptMapStep">
    SELECT did, dname FROM dept WHERE did = #{did}
</select>

<resultMap type="Dept" id="deptMapStep">
    <id column="did" property="did"/>
    <result column="dname" property="dname"/>
    <collection column="did" property="emps"
            select="cn.edu.nuist.mapper.EmpDeptMapper.getEmpListByDid">
    </collection>
</resultMap>

延迟加载

在分步查询的基础上,可以使用延迟加载来提升查询的效率,只需要在全局的 Settings 中进行如下的配置(延迟加载只对分步查询有效果)。

完成 mybatis-config.xml 的相关配置:

<settings>
    <!-- 开启延迟加载 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!-- 设置加载的数据是按需还是全部 -->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

  • association 测试代码
    public void test() throws IOException {
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        SqlSession session = factory.openSession(true);
        EmpDeptMapper mapper = session.getMapper(EmpDeptMapper.class);
        // 查询员工!
        Emp emp = mapper.getEmpStep("4");
        System.out.println(emp.getEname());
        System.out.println("===============");
        System.out.println(emp.getDept());
    }
    
  • 打印结果1:延迟加载开启前
    ==> Preparing: SELECT * FROM emp WHERE eid = ?
    ==> Parameters: 4(String)  (BaseJdbcLogger.java:145)
    ====> Preparing: SELECT did, dname FROM dept WHERE did=?
    ====> Parameters: 4(Integer) (BaseJdbcLogger.java:145)
    <==== Total: 1 (BaseJdbcLogger.java:145)
    <== Total: 1 (BaseJdbcLogger.java:145)
    shaw
    ===============
    Dept [did=4, dname=财务部]
    
  • 打印结果2:延迟加载开启后
    ==> Preparing: SELECT * FROM emp WHERE eid = ?
    ==> Parameters: 4(String)  (BaseJdbcLogger.java:145)
    <== Total: 1  (BaseJdbcLogger.java:145)
    shaw
    ===============
    ==> Preparing: SELECT did, dname FROM dept WHERE did=?
    ==> Parameters: 4(Integer)  (BaseJdbcLogger.java:145)
    <== Total: 1  (BaseJdbcLogger.java:145)
    Dept [did=4, dname=财务部]
    

  • collection 测试代码
    @Test
    public void test2() throws IOException {
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        SqlSession session = factory.openSession(true);
        EmpDeptMapper mapper = session.getMapper(EmpDeptMapper.class);
        Dept dept = mapper.getOnlyDeptInfoByDid("1");
        System.out.println(dept.getDname());
        System.out.println(dept.getEmps());
    }
    
  • fetchType="lazy|eager" 可单独指定某一个分步查询是否延迟加载
    <!-- Dept getOnlyDeptInfoByDid(String did); -->
    <select id="getOnlyDeptInfoByDid" resultMap="deptMapStep">
        <!--
            如果分步查询时,需要传递给调用的查询中有多个参数,则需要将多个参
            数封装成 Map 来进行传递,语法如下: {k1=v1, k2=v2....}
            在所调用的查询 SQL,取值时就要参考 Map 的取值方式,需要严格的按
            照封装 Map 时所用的 key 来取值
            ························································
            即如下 #{} 的内容必须与 {did=did} 的 key 相同
        -->
        SELECT did, dname FROM dept WHERE did = #{did}
    </select>
    
    <!-- List<Emp> getEmpListByDid(String did); -->
    <select id="getEmpListByDid" resultType="Emp">
        SELECT * FROM emp WHERE did = #{did}
    </select>
    
    <resultMap type="Dept" id="deptMapStep">
        <id column="did" property="did"></id>
        <result column="dname" property="dname"/>
        <!--
        fetchType="lazy|eager"
            在 <association> 和 <collection> 标签中都可以设置 fetchType
        可单独指定某一个分步查询是否延迟加载。
            fetchType 可以灵活的设置查询是否需要使用延迟加载,而不需要因为
        某个查询不想使用延迟加载将全局的延迟加载设置关闭。
        -->
        <collection property="emps" column="{did=did}" fetchType="lazy"
                select="cn.edu.nuist.mapper.EmpDeptMapper.getEmpListByDid" />
    </resultMap>
    
  • 打印结果
    Preparing: SELECT did, dname FROM dept WHERE did = ?
    Parameters: 1(String)
    Preparing: SELECT * FROM emp WHERE did = ?
    Parameters: 1(Integer)
    Total: 2
    Total: 1
    公关部
    [Emp [eid=1, ename=Finch, age=40, sex=男, dept=null]
    , Emp [eid=11, ename=Samaritan, age=999, sex=999, dept=null]]
    =================↓ lazy ↓=================↑ eager ↑=================
    ==> Preparing: SELECT did, dname FROM dept WHERE did = ?
    ==> Parameters: 1(String)
    <== Total: 1
    公关部
    ==> Preparing: SELECT * FROM emp WHERE did = ?
    ==> Parameters: 1(Integer)
    <== Total: 2
    [Emp [eid=1, ename=Finch, age=40, sex=男, dept=null]
    , Emp [eid=11, ename=Samaritan, age=999, sex=999, dept=null]]
    

ofType 和 javaType

摘自:https://blog.csdn.net/u013216156/article/details/78642920/

javaType 和 ofType 都是用来指定对象类型的,但是 javaType 是用来指定 POJO 中属性的类型,而 ofType 指定的是映射到 List 集合属性中 POJO 的类型。

  • POJO
    public class User {
        private int id;
        private String username;
        private String mobile;
        private List<Post> posts;
    
        // ...
    }
    
  • UserMapper.xml
    <resultMap type="User" id="resultUserMap">
        <result property="id" javaType="int" column="user_id" />
        <result property="username" javaType="string" column="username" />
        <result property="mobile"  column="mobile" />
        <!--
            javatype 指定的是 User 对象的属性的类型(例如 id,posts)
            ofType 指定的是映射到 List 集合属性中 POJO 的类型(本例指的是 post 类型)
         -->
        <collection property="posts" ofType="com.spenglu.Post"
                javaType="java.util.ArrayList" column="userid">
            <id property="id" column="post_id"
                    javaType="int" jdbcType="INTEGER"/>
            <result property="title" column="title"
                    javaType="string" jdbcType="VARCHAR"/>
            <result property="content" column="content"
                    javaType="string" jdbcType="VARCHAR"/>
        </collection>
    </resultMap>
    
posted @ 2020-08-31 19:53  tree6x7  阅读(102)  评论(0编辑  收藏  举报