MyBatis 之二(映射文件)

4、MyBatis 映射文件

映射文件指导着 MyBatis 如何进行数据库增删改查,有着非常重要的意义;

  • cache –命名空间的二级缓存配置

  • cache-ref –其他命名空间缓存配置的引用。

  • resultMap–自定义结果集映射

  • parameterMap –已废弃!老式风格的参数映射

  • sql –抽取可重用语句块。

  • insert –映射插入语句

  • update –映射更新语句

  • delete –映射删除语句

  • select –映射查询语句

4.1 insert、update、delete元素

在这里插入图片描述
(1)mybatis 允许增删改直接定义以下类型返回值:Integer、Long、Boolean、void

(2)需要手动提交数据

  • sqlSessionFactory.openSession();===>手动提交
  • sqlSessionFactory.openSession(true);===>自动提交

EmployeeMapper.java

public interface EmployeeMapper {
    
    

    public Employee getEmpById(Integer id);

    public Long addEmp(Employee employee);

    public boolean updateEmp(Employee employee);

    public void deleteEmpById(Integer id);
}

EmployeeMapper.xml

	<insert id="addEmp" parameterType="com.mycode.mybatis.bean.Employee"
            useGeneratedKeys="true" keyProperty="id">
        insert into tbl_employee(last_name,email,gender)
        values(#{lastName},#{email},#{gender})
    </insert>

    <!-- public void updateEmp(Employee employee); -->
    <update id="updateEmp">
        update tbl_employee set last_name=#{lastName},email=#{email},gender=#{gender}
        where id=#{id}
    </update>

    <!-- public void deleteEmpById(Integer id); -->
    <delete id="deleteEmpById">
        delete from tbl_employee where id=#{id}
    </delete>
	@Test
    public void test03() throws IOException {
    
    
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        // 1、获取到的 SqlSession 不会自动提交数据

        try(SqlSession openSession = sqlSessionFactory.openSession()) {
    
    
            EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);

            // 测试添加
             Employee employee = new Employee(null, "Jerry", "[email protected]", "1");
             mapper.addEmp(employee);
            System.out.println(employee.getId());

            //测试修改
//            Employee employee = new Employee(1, "Tom", "[email protected]", "0");
//            boolean updateEmp = mapper.updateEmp(employee);
//            System.out.println(updateEmp);

            // 测试删除
//            mapper.deleteEmpById(2);

            // 2、手动提交数据
            openSession.commit();
        }
    }

(3)主键生成方式

① 若数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server),则可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置到目标属性上。
mysql 支持自增主键,自增主键值的获取,mybatis 也是利用 statement.getGeneratedKeys()
在这里插入图片描述

  • useGeneratedKeys=“true”:使用自增主键获取主键值策略
  • keyProperty:指定对应的主键属性,也就是 mybatis 获取到主键值以后,将这个值封装给 javaBean 的哪个属性

② 而对于不支持自增型主键的数据库(例如 Oracle,Oracle 使用序列来模拟自增;每次插入的数据的主键是从序列中拿到的值),则可以使用 selectKey 子元素:selectKey 元素将会首先运行,id 会被设置,然后插入语句会被调用

	<insert id="addEmp" databaseId="oracle">
        <!--
            keyProperty:查出的主键值封装给 javaBean 的哪个属性
            order="BEFORE":当前 sql 在插入 sql 之前运行
                   AFTER:当前 sql 在插入 sql 之后运行
            resultType:查出的数据的返回值类型

            BEFORE 运行顺序:
                先运行 selectKey 查询 id 的 sql;查出 id 值封装给 javaBean 的 id 属性
                再运行插入的 sql;就可以取出 id 属性对应的值
            AFTER 运行顺序:
                先运行插入的 sql(从序列中取出新值作为 id);
                再运行 selectKey 查询 id 的 sql;
        -->
        <selectKey keyProperty="id" order="BEFORE" resultType="Integer">
            <!-- 编写查询主键的 sql 语句 -->
            <!-- BEFORE -->
            select EMPLOYEES_SEQ.nextval from dual

            <!-- AFTER 
            select EMPLOYEES_SEQ.currval from dual -->
        </selectKey>
        <!-- 插入时的主键是从序列中拿到的 -->
        <!-- BEFORE:-->
        insert into employees(EMPLOYEE_ID,LAST_NAME,EMAIL)
        values(#{id},#{lastName},#{email})

        <!-- AFTER:
        insert into employees(EMPLOYEE_ID,LAST_NAME,EMAIL)
        values(employee_seq.nextval,#{lastName},#{email}) -->
    </insert>
  • selectKey
    在这里插入图片描述

4.2 参数(Parameters)传递

(1)单个参数

可以接受基本类型,对象类型,集合类型的值。这种情况 MyBatis 可直接使用这个参数,不需要经过任何处理。

#{参数名}:取出参数。

(2)多个参数

任意多个参数,都会被 MyBatis 重新包装成一个 Map 传入。Map 的 key 是 param1,param2,arg0,arg1…,值就是参数的值。

异常:

org.apache.ibatis.binding.BindingException:
Parameter ‘id’ not found.
Available parameters are [arg1, arg0, param1, param2]

操作:

  • 方法:public Employee getEmpByIdAndLastName(Integer id,String lastName);
  • 取值:#{id},#{lastName}

(3)命名参数

为参数使用 @Param 起一个名字,MyBatis 就会将这些参数封装进 map 中,key 就是我们自己指定的名字

明确指定封装参数时 map 的 key ,@Param(“id”),多个参数会被封装成一个 map

  • key:使用 @Param 注解指定的值
  • value:参数值
  • #{指定的key}取出对应的参数值
public Employee getEmpByIdAndLastName(@Param("id") Integer id,@Param("lastName") String lastName);
	<select id="getEmpByIdAndLastName" resultType="com.mycode.mybatis.bean.Employee">
        select * from tbl_employee where id=#{id} and last_name=#{lastName}
    </select>

(4)POJO

如果多个参数正好是我们业务逻辑的数据模型,就可以直接传入 POJO

#{属性名}:取出传入的 pojo 的属性值

(5)Map

如果多个参数不是业务模型中的数据,没有对应的 pojo,不经常使用,为了方便,我们也可以传入 map

#{key}:取出 map 中对应的值

(6)TO

如果多个参数不是业务模型中的数据,但是经常要使用,推荐来编写一个 TO(Transfer Object) 数据传输对象

思考:

  • public Employee getEmp(@Param(“id”)Integer id,String lastName);
    取值:id => #{id/param1} lastName => #{param2}

  • public Employee getEmp(Integer id,@Param(“e”)Employee emp);
    取值:id => #{param1} lastName => #{param2.lastName/e.lastName}

特别注意:如果是 Collection(List,Set)类型或者是数组,也会特殊处理。
也是把传入的 list 或者数组封装在 map 中。

  • key:Collection(collection),如果是 List 还可以使用这个 key(list)、数组(array)

  • 例:public Employee getEmpById(List<Integer> ids);
    取值:取出第一个 id 的值:#{list[0]}

4.3 结合源码分析 mybatis 怎么处理参数

(@Param("id") Integer id,@Param("lastName") String lastName)

ParaNameResolver 解析参数封装成 map

(1)names:{0=id, 1=lastName} 构造器的时候就确定好了

确定流程:
① 获取每个标了 param 注解的参数的 @Param 值:id,lastName,赋值给name

② 每次解析一个参数给 map 中保存信息:(key:参数索引,value:name的值)
name的值:

  • 标注了 param 注解:注解的值
  • 没有标注:
    • 全局配置:useActualParamName(jdk1.8):name=参数名
    • name=map.size(); 相当于当前元素的索引
      {0=id, 1=lastName, 2=2}
args  [1,"Tom","hello"]
public Object getNamedParams(Object[] args) {
    
    
    int paramCount = this.names.size();
    // 1、参数为 null 直接返回
    if (args != null && paramCount != 0) {
    
    

        // 2、如果只有一个元素,并且没有 Param 注解;args[0]:单个参数直接返回
        if (!this.hasParamAnnotation && paramCount == 1) {
    
    
            Object value = args[(Integer)this.names.firstKey()];
            return wrapToMapIfCollection(value, this.useActualParamName ? (String)this.names.get(0) : null);

        // 3、多个元素或者有 Param 标注
        } else {
    
    
            Map<String, Object> param = new ParamMap();
            int i = 0;

            // 4、遍历 names 集合;{0=id, 1=lastName, 2=2}
            for(Iterator var5 = this.names.entrySet().iterator(); var5.hasNext(); ++i) {
    
    
                Entry<Integer, String> entry = (Entry)var5.next();
                // names 集合的 value 作为 key;names 集合的 key 又作为取值的参考 args[0]:args  [1,"Tom","hello"]
                // eg: {id=args[0]:1,lastName=args[1]:Tom,2=args[2]:hello}
                param.put(entry.getValue(), args[(Integer)entry.getKey()]);

                // 额外的将每一个参数也保存到 map 中,使用新的 key:param1...paramN
                // 效果:有 Param 注解可以#{指定的key},或者#{param1}
                String genericParamName = "param" + (i + 1);
                if (!this.names.containsValue(genericParamName)) {
    
    
                    param.put(genericParamName, args[(Integer)entry.getKey()]);
                }
            }

            return param;
        }
    } else {
    
    
        return null;
    }
}

4.4 参数值的获取

#{}:可以获取 map 中的值或者 pojo 对象属性的值;

${}:可以获取 map 中的值或者 pojo 对象属性的值;

select * from tbl_employee where id=${id} and last_name=#{lastName}
Preparing: select * from tbl_employee where id=1 and last_name=?

区别:

  • #{}:是以预编译的形式,将参数设置到 sql 语句中;PreparedStatement;防止 sql 注入
  • ${}:取出的值直接拼装在 sql 语句中;会有安全问题
    大多情况下,我们取参数的值都应该去使用#{}

原生 jdbc 不支持占位符的地方,我们就可以使用 ${} 进行取值,比如分表、排序…:按照年份分表拆分
select * from ${year}_salary where xxx;
select * from tbl_employee order by ${f_name} ${order}

#{}:更丰富的用法:
规定参数的一些规则:
javaType、jdbcType、mode(存储过程)、numericScale(保留几位小数)、
resultMap(封装的结果类型)、typeHandler(处理数据的类型处理器)、
jdbcTypeName、expression(未来准备支持的功能)

javaType 通常可以从参数对象中来去确定
如果null 被当作值来传递,对于所有可能为空的列,jdbcType 需要被设置
对于数值类型,还可以设置小数点后保留的位数:
mode 属性允许指定IN,OUT 或INOUT 参数。如果参数为OUT 或INOUT,参数对象属性的真实值将会被改变,就像在获取输出参数时所期望的那样。

  • jdbcType 通常需要在某种特定的条件下被设置:
    在我们数据为 null 的时候,有些数据库可能不能识别 mybatis 对 null 的默认处理。比如 Oracle(报错)

  • JdbcType OTHER:无效的类型,因为 mybatis 对所有的 null 都映射的是原生 Jdbc 的 OTHER 类型,oracle 不能正确处理

  • 由于全局配置中:jdbcTypeForNull=OTHER; oracle 不支持,两种解决办法
    1、#{email,jdbcType=OTHER}
    2、jdbcTypeForNull=NULL
    <setting name=“jdbcTypeForNull” value=“NULL”/>

4.5 select 元素

Select元素来定义查询操作。

  • Id:唯一标识符。
    –用来引用这条语句,需要和接口的方法名一致

  • parameterType:参数类型。
    –可以不传,MyBatis 会根据 TypeHandler 自动推断

  • resultType:返回值类型。
    –别名或者全类名,如果返回的是集合,定义集合中元素的类型。不能和resultMap同时使用

① 返回集合public List<Employee> getEmpsByLastNameLike(String lastName);

	<!-- resultType:如果返回的是一个集合,要写集合中元素的类型-->
    <select id="getEmpsByLastNameLike" resultType="com.mycode.mybatis.bean.Employee">
        select * from tbl_employee where last_name like #{lastName}
    </select>

② 返回一条记录的 map,key 就是列名,值就是对应的值public Map<String, Object> getEmpByIdReturnMap(Integer id);

	<select id="getEmpByIdReturnMap" resultType="map">
        select * from tbl_employee where id=#{id}
    </select>

③ 多条记录封装成一个 map,Map<Integer, Employee>:键是这条记录的主键。值是记录封装后的 javaBean

	// 告诉 mybatis 封装这个 map 的时候使用哪个属性作为 map 的 key
    @MapKey("lastName")
    public Map<String,Employee> getEmpByLastNameLikeReturnMap(String lastName);
	<select id="getEmpByLastNameLikeReturnMap" resultType="com.mycode.mybatis.bean.Employee">
        select * from tbl_employee where last_name like #{lastName}
    </select>

在这里插入图片描述

4.6 自动映射

(1)全局 setting 设置

  • autoMappingBehavior 默认是 PARTIAL,开启自动映射的功能。唯一的要求是列名和 javaBean 属性名一致

  • 如果 autoMappingBehavior 设置为 null 则会取消自动映射

  • 数据库字段命名规范,POJO 属性符合驼峰命名法,如A_COLUMN–>aColumn,我们可以开启自动驼峰命名规则映射功能,mapUnderscoreToCamelCase=true。

(2)自定义 resultMap,实现高级结果集映射

resultMap

  • constructor:类在实例化时, 用来注入结果到构造方法中

    • idArg:ID 参数; 标记结果作为 ID 可以帮助提高整体效能
    • arg:注入到构造方法的一个普通结果
  • id:一个ID 结果; 标记结果作为 ID 可以帮助提高整体效能

  • result:注入到字段或 JavaBean 属性的普通结果

  • association:一个复杂的类型关联;许多结果将包成这种类型
    嵌入结果映射,结果映射自身的关联,或者参考一个

  • collection:复杂类型的集

    • 嵌入结果映射,结果映射自身的集,或者参考一个
  • discriminator:使用结果值来决定使用哪个结果映射

    • case:基于某些值的结果映射
    • 嵌入结果映射–这种情形结果也映射它本身,因此可以包含很多相同的元素,或者它可以参照一个外部的结果映射。

① id & result

  • id 和 result 映射一个单独列的值到简单数据类型(字符串,整型,双精度浮点数,日期等)的属性或字段。
    在这里插入图片描述

② association

  • 复杂对象映射
  • POJO 中的属性可能会是一个对象
  • 我们可以使用联合查询,并以级联属性的方式封装对象。
	<resultMap id="MyDifEmp" type="com.mycode.mybatis.bean.Employee">
        <id column="id" property="id"/>
        <result column="last_name" property="lastName" />
        <result column="gender" property="gender"/>
        <result column="did" property="dept.id"/>
        <result column="dept_name" property="dept.departmentName"/>
    </resultMap>
  • 使用 association 标签定义对象的封装规则

association 嵌套结果集

	<resultMap id="MyDifEmp2" type="com.mycode.mybatis.bean.Employee">
        <id column="id" property="id"/>
        <result column="last_name" property="lastName" />
        <result column="gender" property="gender"/>

        <!-- association 可以指定联合的 javaBean 对象
            property="dept":指定哪个属性是联合的对象
            javaType:指定这个属性对象的类型[不能省略]
        -->
        <association property="dept" javaType="com.mycode.mybatis.bean.Department">
            <id column="did" property="id"/>
            <result column="dept_name" property="departmentName"/>
        </association>
    </resultMap>

association 分段查询

	<!-- 使用 association 进行分步查询:
        1、先按照员工 id 查询员工信息
        2、根据查询员工信息中的 d_id 值去部门表查出部门信息
        3、部门设置到员工中
     -->
    <resultMap id="MyEmpByStep" type="com.mycode.mybatis.bean.Employee">
        <id column="id" property="id"/>
        <result column="last_name" property="lastName"/>
        <result column="email" property="email"/>
        <result column="gender" property="gender"/>
        <!-- association 定义关联对象的封装规则
            select:表明当前属性是调用 select 指定的方法查出的结果
            column:指定将哪一列的值传给这个方法

            流程:使用 select 指定的方法(传入 column 指定的这列参数的值)查出对象,并封装给 property 指定的属性
        -->
        <association property="dept"
                     select="com.mycode.mybatis.dao.DepartmentMapper.getDeptById"
                     column="d_id">
        </association>
    </resultMap>

association 分段查询&延迟加载

  • 开启延迟加载和属性按需加载
    使用延迟加载(懒加载)(按需加载)
    部门信息在我们使用的时候再去查询
    分段查询的基础之上加上两个配置:
	<settings>
		<setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
  • 旧版本的MyBatis需要额外的支持包
    –asm-3.3.1.jar
    –cglib-2.2.2.jar

③ Collection

  • Collection 集合类型&嵌套结果集
	<!-- public Department getDeptByIdPlus(Integer id); -->
    <select id="getDeptByIdPlus" resultMap="MyDept">
        SELECT d.`id` did,d.`dept_name` dept_name,
                e.`id` eid,e.`last_name` last_name,e.`email` email,e.`gender` gender
        FROM tbl_dept d
        LEFT JOIN tbl_employee e
        ON d.`id`=e.`d_id`
        WHERE d.`id`=#{id}
    </select>
	<!-- 嵌套结果集的方式,使用 collection 标签定义关联的集合类型的属性封装规则 -->
    <resultMap id="MyDept" type="com.mycode.mybatis.bean.Department">
        <id column="did" property="id"/>
        <result column="dept_name" property="departmentName"/>
        <!--
            collection 定义关联集合类型的属性的封装规则
            ofType:指定集合里面元素的类型
        -->
        <collection property="emps" ofType="com.mycode.mybatis.bean.Employee">
            <!-- 定义这个集合中元素的封装规则 -->
            <id column="eid" property="id"/>
            <result column="last_name" property="lastName"/>
            <result column="email" property="email"/>
            <result column="gender" property="gender"/>
        </collection>
    </resultMap>
  • Collection 分步查询&延迟加载
	<!-- public Department getDeptByIdStep(Integer id); -->
    <select id="getDeptByIdStep" resultMap="MyDeptStep">
        select id,dept_name from tbl_dept where id=#{id}
    </select>
	<resultMap id="MyDeptStep" type="com.mycode.mybatis.bean.Department">
        <id column="id" property="id"/>
        <result column="dept_name" property="departmentName"/>
        <collection property="emps"
                    select="com.mycode.mybatis.dao.EmployeeMapperPlus.getEmpsByDeptId"
                    column="id">
        </collection>
    </resultMap>

扩展:多列值封装 map 传递

  • 分步查询的时候通过 column 指定,将对应的列的数据传递过去,我们有时需要传递多列数据。
  • 使用 {key1=column1,key2=column2…} 的形式
	<resultMap id="MyDeptStep" type="com.mycode.mybatis.bean.Department">
        <id column="id" property="id"/>
        <result column="dept_name" property="departmentName"/>
        <collection property="emps"
                    select="com.mycode.mybatis.dao.EmployeeMapperPlus.getEmpsByDeptId"
                    column="{deptId=id}" fetchType="eager">
        </collection>
    </resultMap>
  • association 或者 collection 标签的 fetchType=eager/lazy 可以覆盖全局的延迟加载策略,指定立即加载(eager)或者延迟加载(lazy)

④ discriminator

  • mybatis 可以使用 discriminator 判断某列的值,然后根据某列的值改变封装行为
  • column:指定判断的列名
  • javaType:列值对应的 java 类型
	<!-- 
         封装 Employee:
                如果查出的是女生:就把部门信息查询出来,否则不查询
                如果是男生,把 last_name 这一列的值赋值给 email
    -->
    <resultMap id="MyEmpDis" type="com.mycode.mybatis.bean.Employee">
        <id column="id" property="id"/>
        <result column="last_name" property="lastName"/>
        <result column="email" property="email"/>
        <result column="gender" property="gender"/>
        
        <discriminator javaType="string" column="gender">
            <!-- 女生 resultType:指定封装的结果类型,不能缺少。resultMap -->
            <case value="0" resultType="com.mycode.mybatis.bean.Employee">
                <association property="dept"
                    select="com.mycode.mybatis.dao.DepartmentMapper.getDeptById"
                    column="d_id">
                </association>
            </case>
            <!-- 男生  如果是男生,把 last_name 这一列的值赋值给 email -->
            <case value="1" resultType="com.mycode.mybatis.bean.Employee">
                <id column="id" property="id"/>
                <result column="last_name" property="lastName"/>
                <result column="last_name" property="email"/>
                <result column="gender" property="gender"/>
            </case>
        </discriminator>
    </resultMap>

猜你喜欢

转载自blog.csdn.net/Lu1048728731/article/details/112658139