MyBatis框架高级应用详解

时间:2020-07-11
本文章向大家介绍MyBatis框架高级应用详解,主要包括MyBatis框架高级应用详解使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

项目已托管到GitHub,大家可以去GitHub查看下载!并搜索关注微信公众号 码出Offer 领取各种学习资料!

MyBatis

一、ORM概述

对象关系映射(Object Relational Mapping,简称ORM)是通过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中。本质上就是将数据从一种形式转换到另外一种形式。

ORM

二、ORM映射

2.1 MyBatis自动ORM失效

MyBatis只能自动维护库表“列名”与“属性名”相同时的一一对应关系,二者不同时,无法自动ORM。

自动ORM失效
007

2.2 解决方式一:列的别名

在SQL中使用 as 为查询字段添加列别名,以匹配属性名。

<mapper namespace="com.mylifes1110.dao.ManagerDao">
    <select id="selectManagerByIdAndPwd" resultType="com.mylifes1110.bean.Manager">
        SELECT mgr_id AS id , mgr_name AS username , mgr_pwd AS password
        FROM t_managers
        WHERE mgr_id = #{id} AND mgr_pwd = #{pwd}
    </select>
</mapper>

2.3 解决方式二:结果映射

使用<resultMap id="别名" type="实体类对象名" >标签来映射,匹配列名与属性名。

注意: property设置属性,column设置别名

<mapper namespace="com.mylifes1110.dao.ManagerDao">

    <!--定义resultMap标签-->
    <resultMap id="managerResultMap" type="com.mylifes1110.bean.Manager">
          <!--关联主键与列名-->
        <id property="id" column="mgr_id" />

          <!--关联属性与列名-->
        <result property="username" column="mgr_name" />
        <result property="password" column="mgr_pwd" />
    </resultMap>

     <!--使用resultMap作为ORM映射依据-->
    <select id="selectAllManagers" resultMap="managerResultMap">
        SELECT mgr_id , mgr_name , mgr_pwd
        FROM t_managers
    </select>
</mapper>

三、MyBatis处理关联关系

3.1 映射关系

实体间的关系: 关联关系(拥有 has、属于 belong)

  • OneToOne: 一对一关系(Passenger--- Passport)

  • OneToMany: 一对多关系(Employee --- Department)

  • ManyToMany: 多对多关系(Student --- Subject)

3.2 映射表分析

Table建立外键关系
008
Entity添加关系属性
009_2
Mapper中将属性与列名对应
010

3.3 映射关系应用

3.3.1 标签说明
  • 结果映射标签: <resultMap id="结果映射别名" type="实体类对象">

  • 双方均可建立关系属性,建立关系属性后,对应的Mapper文件中需使用<ResultMap >完成多表映射

  • id映射标签: <id property="ID名" column="ID别名" />

  • 属性映射标签: <result property="属性名" column="别名" />

  • 映射单一对象标签: <association property="对象属性名" javaType="实体类包含的单一对象">

  • 持有对象关系属性使用<association>标签来完成映射,此标签是写在<resultMap>标签内

  • 映射集合对象标签: <collection property="集合属性名" ofType="集合泛型内单一对象">

  • 持有集合关系属性,使用<collection>标签来完成映射,此标签是写在<resultMap>标签内

  • 查询标签: <select id="接口方法名" resultMap="结果映射别名">

  • 查询标签中resultMap属性内填入的是结果映射别名

3.3.2 一对一关系应用

在一对一关系中,如果实体类中包含需要查询的对象,则需要在<resultMap>标签内添加<association>标签来映射实体类中的单一对象

创建表

// 旅客表
create table passenger
(
    id       int auto_increment
        primary key,
    name     varchar(50null,
    sex      tinyint     null,
    birthday date        null
);

// 护照表
create table passport
(
    id           int auto_increment
        primary key,
    nationlity   varchar(100null,
    expire       date         null,
    passenger_id int          null
);

创建实体类对象

package com.mylifes1110.bean;

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

import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Passport {
    private int id;
    private String nationlity;
    private Date expire;
    private int passengerId;
    private Passenger passenger;

    public Passport(String nationlity, Date expire, int passengerId) {
        this.nationlity = nationlity;
        this.expire = expire;
        this.passengerId = passengerId;
    }
}

package com.mylifes1110.bean;

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

import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Passenger {
    private int id;
    private String name;
    private boolean sex;
    private Date birthday;
    private Passport passport;

    public Passenger(String name, boolean sex, Date birthday) {
        this.name = name;
        this.sex = sex;
        this.birthday = birthday;
    }
}

创建接口

package com.mylifes1110.dao;

import com.mylifes1110.bean.Passenger;
import org.apache.ibatis.annotations.Param;

public interface PassengerDao {
    Passenger selectPassengerById(@Param("id") int id);
}

创建Mapper.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">

<!--namespace:所需实现的接口全限定名-->
<mapper namespace="com.mylifes1110.dao.PassengerDao">
    <!--封装结果映射-->
    <resultMap id="passenger_passport" type="com.mylifes1110.bean.Passenger">
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
        <result column="sex" property="sex"></result>
        <result column="birthday" property="birthday"></result>
        <!--封装类中对象-->
        <association property="passport" javaType="com.mylifes1110.bean.Passport">
            <id column="id" property="id"></id>
            <result column="nationlity" property="nationlity"></result>
            <result column="expire" property="expire"></result>
        </association>
    </resultMap>

<!--查询-->
<select id="selectPassengerById" resultMap="passenger_passport">
    select passenger.id,
       passenger.name,
       passenger.sex,
       passenger.birthday,
       passport.id pid,
       passport.nationlity,
       passport.expire,
       passport.passenger_id
from passenger
         join passport on passenger.id = passport.passenger_id
where passenger.id = #{id}
</select>
</mapper>

注册Mapper

<!--Mapper注册-->
<mappers>
    <mapper resource="mappers/PassengerMapper.xml"/>
<mappers>

测试类

@Test
public void selectPassengerById() {
    PassengerDao passengerDao = MyBatisUtils.getMapper(PassengerDao.class);
    System.out.println(passengerDao.selectPassengerById(1));
}
3.3.3 一对多关系应用

在一对多关系中,可能会出现查询一个对象(实体)的信息,还有可能会出现查询好多对象(实体集合)的信息。那么我们可以判断在查询一个对象的信息是可以使用<association>标签,而查询集合对象的信息可以使用<collection>标签

创建表

// 部门
create table department
(
    id       int auto_increment
        primary key,
    name     varchar(100null,
    location varchar(200null
);

// 员工
create table employee
(
    id      int auto_increment
        primary key,
    name    varchar(100null,
    salary  double       null,
    dept_id int          null
);

实体类

package com.mylifes1110.bean;

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

import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {
    private int id;
    private String name;
    private double salary;
    private int deptId;
    private List<Department> departments;
}

package com.mylifes1110.bean;

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

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {
    private int id;
    private String name;
    private String location;
    private Employee employee;
}

Dao层接口

package com.mylifes1110.dao;

import com.mylifes1110.bean.Employee;

import java.util.List;

public interface EmployeeDao {
    List<Employee> selectAllEmployee();
}

Mapper.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">

<!--namespace:所需实现的接口全限定名-->
<mapper namespace="com.mylifes1110.dao.EmployeeDao">
    <resultMap id="selectEmployeeAll" type="com.mylifes1110.bean.Employee">
        <id property="id" column="eid"></id>
        <result property="name" column="ename"></result>
        <result property="salary" column="salary"></result>
        <collection property="departments" ofType="com.mylifes1110.bean.Department">
            <id property="id" column="id"></id>
            <result property="name" column="name"></result>
            <result property="location" column="location"></result>
        </collection>
    </resultMap>
<select id="selectAllEmployee" resultMap="selectEmployeeAll">
    select department.id, department.name, department.location, employee.id eid, employee.name ename, employee.salary
from department
         join employee on department.id = employee.dept_id;
</select>
</mapper>

测试类

@Test
public void selectAllEmployee() {
    EmployeeDao employeeDao = MyBatisUtils.getMapper(EmployeeDao.class);
    System.out.println(employeeDao.selectAllEmployee());
}
3.3.4 多对多关系应用

关于多对多在映射中还算是有挑战性的,接下来是一个三表联查来实现多对多映射

创建表

// 科目表
create table subject(
   id int primary key auto_increment,
   name varchar(100),
   grade int
)default charset =utf8;

// 学生表
create table student(
    id int primary key  auto_increment,
    name varchar(50),
    sex tinyint
)default charset =utf8;

// 学生和科目的中间管理表
create table student_subject(
  student_id int references student(id),
  subject_id int references subject(id),
  primary key (student_id,subject_id)
)default charset =utf8;
建立第三张关系表
011_2

实体类

package com.mylifes1110.bean;

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

import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    private int id;
    private String name;
    private boolean sex;
    private List<Subject> subjects;
}

package com.mylifes1110.bean;

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

import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Subject {
    private int id;
    private String name;
    private int grade;
    private List<Student> students;
}

Dao层接口

package com.mylifes1110.dao;

import com.mylifes1110.bean.Subject;

import java.util.List;

public interface StudentDao {
    List<Subject> selectAllStudent();
}

Mapper.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">

<!--namespace = 所需实现的接口全限定名-->
<mapper namespace="com.mylifes1110.dao.StudentDao">
<resultMap id="subject_student" type="com.mylifes1110.bean.Subject">
    <id property="id" column="id"></id>
    <result property="name" column="name"></result>
    <result property="grade" column="grade"></result>
<collection property="students" ofType="com.mylifes1110.bean.Student">
    <id property="id" column="stuid"></id>
    <result property="name" column="stuname"></result>
    <result property="sex" column="sex"></result>
</collection>
</resultMap>
<select id="selectAllStudent" resultMap="subject_student">
select student.id stuid, student.name stuname, student.sex, subject.id, subject.name, subject.grade
from student
         join student_subject on student.id = student_subject.student_id
         join subject
              on student_subject.subject_id = subject.id;
</select>
</mapper>

测试类

@Test
public void selectAllStudent() {
    StudentDao studentDao = MyBatisUtils.getMapper(StudentDao.class);
    System.out.println(studentDao.selectAllStudent());
}

四、动态SQL

4.1 动态SQL概述

MyBatis的映射文件中支持在基础SQL上添加一些逻辑操作,并动态拼接成完整的SQL之后再执行,以达到SQL复用、简化编程的效果

4.2 sql标签

关于<sql>标签是用来抽取多个相同重复sql片段的标签,抽取后并书写此标签内,随后再为<sql id="别名">标签内取一个别名。当需要引入重复sql片段时,使用<include refid="sql标签别名">标签来完成

<sql id="selectSql">
    select id, username, password, gender, birth
</sql>
<!--sql标签使用-->
<select id="selectUserById1" resultType="com.mylifes1110.bean.User">
    <include refid="selectSql"></include>
    from tb_user where id = #{id}
</select>

4.3 if标签

关于<if>标签,它适用于是一个场景。我们的以前的增删改查一旦写了条件就写死了,导致很不灵活。比如:在select查询语句中定义了多个查询条件,而这个当查询的时候必须写入这几个查询条件,缺一个都不行。当项目需求改了时怎么办呢?只能需要修改代码来实现,而<if>标签能够动态的来解除多条件的限制,当我们考虑好这几个条件是可以实现动态条件查询的时候,就可以使用<if>标签来分配

// 如果username不为空并且不为空字符串则username的值改为新值

<update id="updateUserById1"
    update tb_user set
    <if test="username != null and username != ''">username = #{username}</if>
    <if test="password != null">password = #{password}</if>
    <if test="gender != null">gender = #{gender}</if>
    <if test="birth != null">birth = #{birth}</if>
    where id = #{id}
</update>

该段代码的意思就是我指定<if>标签包裹的哪一个属性都可以修改,而不是写死的只要修改就必须所有的值都传。那我们看一下测试类,使用<if>标签是怎么动态传值的

@Test
public void updateUserById1() {
    User user = new User(1"Z"nullnullnull);
    UserDao userDao = MyBatisUtils.getMapper(UserDao.class);
    System.out.println(userDao.updateUserById1(user));
}

测试类反映如果我们想改的值就可以直接传入新值修改,不修改的值就只需要传放入null即可

4.4 where标签

<where>标签用于补充Sql中使用的where关键,并且可以自动忽略前缀,比如:and和or前缀

<select id="selectBookByCondition" resultType="com.mylifes1110.bean.Book">
    SELECT id , name , author , publish , sort
    FROM t_books
    <where> 
        <if test="id != null">
            id = #{id}
        </if>

        <if test="name != null">
            and name = #{name}
        </if>

        <if test="author != null">
            and author = #{author}
        </if>

        <if test="publish != null">
            and publish = #{publish}
        </if>

        <if test="sort != null">
            and sort = #{sort}
        </if>
    </where>
</select>

该代码有一种问题场景存在,我们查询的时候不查询id,把id传入null。下一个name前面会多出一个and来干扰我们的查询,普通的情况下使用就会报错。而使用<where>标签来包裹查询条件,既补充了where关键字,又忽略了这种情况下而多出的and关键字。or关键字亦是如此!

4.5 set标签

<set>标签用于补充Sql语句中使用的set关键字,并且可以自动忽略后缀,比如:,(逗号)

<update id="updateBookByCondition">
    UPDATE t_books
    <set>
        <if test="name != null">
            name = #{name} ,
        </if>

        <if test="author != null">
            author = #{author} ,
        </if>

        <if test="publish != null">
            publish = #{publish} ,
        </if>

        <if test="sort != null">
            sort = #{sort} ,
        </if>
    </set>
    WHERE id = #{id}
</update>

同样的思想,该代码也会有一种问题场景存在。我们在修改的时候,修改的值正常都是所有值都传入的,即使不修改的值,也是需要传值的,这样就导致了一种写死的状态,不能动态的修改自己所需要的值。当我们引入<set>标签时,它可以补充set关键字并实现了动态修改自己想要的值。但是第一个值修改时,第二个值不修改的时候,后面就会多出来一个逗号来干扰修改,其<set>标签就解决了此问题,忽略了后缀 , (逗号)

4.6 trim标签

<trim prefix="" suffix="" prefixOverrides="" suffixOverrides="">是一个重量级标签,它代替<where> 和<set>(前缀、后缀、前缀覆盖、后缀覆盖),并具有它们的所有功能

替换<where>标签

<select id="selectBookByCondition" resultType="com.mylifes1110.bean.Book">
        SELECT id,name,author,publish,sort
    FROM t_books
    <!-- 增加WHERE前缀,自动忽略前缀 AND 或 OR -->
    <trim prefix="WHERE" prefixOverrides="AND |OR "
        <if test="id != null">
            and id = #{id}
        </if>

        <if test="name != null">
            and name = #{name}
        </if>

        <if test="author != null">
            and author = #{author}
        </if>

        <if test="publish != null">
            and publish = #{publish}
        </if>

        <if test="sort != null">
            and sort = #{sort}
        </if>
    </trim>
</select>

替换<set>标签

<update id="updateBookByCondition">
    UPDATE t_books
    <!-- 增加SET前缀,自动忽略最后的 逗号 -->
    <trim prefix="SET" suffixOverrides=","
        <if test="name != null">
            name = #{name} ,
        </if>

        <if test="author != null">
            author = #{author} ,
        </if>

        <if test="publish != null">
            publish = #{publish} ,
        </if>

        <if test="sort != null">
            sort = #{sort}
        </if>
    </trim>
    WHERE id = #{id}
</update>

4.7 foreach标签

<foreach>标签可以用来实现批量插入数据和查询数据

参数 描述 取值
collection 容器类型 list、array、map
open 起始符 (
close 结束符 )
separator 分隔符 ,
index 下标号 从0开始,依次递增
item 当前项 任意名称(循环中通过 #{任意名称} 表达式访问)

批量查询

// 实体类
package com.mylifes1110.bean;

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

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

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
    private Integer id;
    private String username;
    private String password;
    private String gender;
    private Date birth;
}

// Dao层接口
List<User> selectUsersBatch(List<Integer> ids);

// Mapper.xml
<select id="selectUsersBatch" resultType="com.mylifes1110.bean.User">
    <include refid="selectUser"></include>
    where id in
    <foreach collection="list" item="id" open="(" close=")" separator=",">
        (#{id})
    </foreach>
</select>

// 测试类
@Test
public void selectUsersBatch() {
    List<Integer> list = Arrays.asList(12345);
    UserDao mapper = MyBatisUtil.getMapper(UserDao.class);
    List<User> users = mapper.selectUsersBatch(list);
    for (User user : users) {
        System.out.println(user);
    }
}

批量插入

// Dao层接口
int insertUserBatch(List<User> users);

// Mapper.xml
<insert id="insertUserBatch">
    insert into tb_user(username, password, gender, birth)
    values
    <foreach collection
="list" item="user" open="" close="" separator=",">
        (#{user.username}, #{user.password}, #{user.gender}, #{user.birth})
    </foreach>
</insert>

// 测试类
@Test
public void insertUserBatch() {
    List<User> list = new ArrayList<>();
    for (int i = 0; i <= 100; i++) {
        User user = new User(null"MM""GGGGGG""f"new Date());
        list.add(user);
    }
    UserDao mapper = MyBatisUtil.getMapper(UserDao.class);
    System.out.println(mapper.insertUserBatch(list));
}

五、缓存

5.1 缓存的