MyBatis最全使用指南

8,551 阅读17分钟

1. 为啥要用 MyBatis

我们作为一个程序员,主要工作归根结底就是和数据打交道。而使用 java 操作数据库的原始方式就是 JDBC

先看看使用 JDBC 方式是如何操作数据库的:

// 1. 加载配置文件
Properties pro=new Properties();
pro.load(new FileReader("resource/jdbc.properties"));
// 2. 获取配置文件中连接数据库的信息
String url=pro.getProperty("url");
String user=pro.getProperty("user");
String password=pro.getProperty("password");
String driver=pro.getProperty("driver");
// 3. 加载数据库的驱动
Class.forName(driver);
// 4. 创建数据库的连接
Connection conn = DriverManager.getConnection(url, user, password);
 // 5. sql 语句
String sql = "select * from s_admin where username=? and password=?";
// 3. 创建执行sql的对象
ps = conn.prepareStatement(sql);
// 4. 给 ?赋值
ps.setString(1, username);
ps.setString(2, password);
// 5. 执行sql
ResultSet rs = ps.executeQuery();
// 6. 如果查询出数据,则返回该条数据
if (rs.next()) {
    Admin admin = new Admin();
    admin.setUsername(rs.getString("username"));
    admin.setPassword(rs.getString("password"));
    return admin;
// 7. 否则返回空
} else {
    return null;
}

看完上面的代码,我们发现了 JDBC 存在的问题:

1.每次操作我们都要创建 connection、Statement 等一些对象,操作完还要关闭、销毁这些对象。

2.ResultSet 不能帮我们完成数据库和实体对象的自动转换,我们还要手动赋值。

3.代码冗余,而且业务操作竟然和数据库操作混在一起,开发效率太低了。

真是越来越不想用 JDBC 了,那有没有一个玩意能帮我解决上面 JDBC 遇到的问题呢?

有的,盖世英雄MyBatis踩着七彩祥云来了。

2. MyBatis 简介

官网地址:

https://mybatis.org/mybatis-3/

MyBatis 是一个基于 java 的持久层框架,它内部封装了 jdbc。

开发人员只需要关注 sql 语句,不需要处理加载驱动、创建连接等繁琐的过程。

MyBatis 通过 xml 或注解两种方式配置 sql 语句,然后执行 sql 并将结果映射为 java 对象并返回。

名词解释:

框架:框架是系统中可复用的设计,其实就相当于一个封装了很多功能的工具。

持久层:就是和数据库打交道的那一层。

3. 环境搭建

1.创建数据库和表

我们使用 Navicat 创建数据库 mybatis_demo,然后创建 user 表。

CREATE TABLE `user` (
  `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '姓名',
  `sex` varchar(1) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '性别',
  `age` int DEFAULT NULL COMMENT '年龄',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;

2.创建 Maven 项目

File -> New -> Project -> Maven 引入依赖

<dependencies>
    <!--  junit 测试  -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!--  mysql驱动  -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.27</version>
    </dependency>
    <!--  mybatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.3</version>
    </dependency>
</dependencies>

3.创建实体类

User 类:

public class User {
    private int id;
    private String name;
    private String sex;
    private int age;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

4.创建 Dao 接口

public interface UserDao {
    /**
     * 获取所有用户信息
     * @return
     */
    List<User>  getAll();
}

5.创建 sql 映射文件

这是一个 mybatis 使用的配置文件,专门用来写 sql 语句的。

它是一个 xml 文件,因为 sql 语句包含在 mapper 标签中,所以又叫 mapper 文件。一般来说一个表对应一个 xml 文件。

规定:文件名称要和接口保持一致。

UserDao.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xxl.dao.UserDao">

  <select id="getAll" resultType="com.xxl.model.User">
      select * from user
  </select>

</mapper>

6.创建 MyBatis 主配置文件

主配置文件也是 xml 文件。

mybatis-config.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>
    
    <!-- 配置 mybatis 输出日志,可以打印 sql-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING" />
    </settings>

    <!-- 配置环境 -->
    <environments default="mysql">
        <!-- 配置 mysql 的环境-->
        <environment id="mysql">
            <!-- 事务的类型-->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 配置数据源 POOLED:表示使用连接池 -->
            <dataSource type="POOLED">
                <!-- 配置连接数据库的信息 -->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo"/>
                <property name="username" value="root"/>
                <property name="password" value="12345678"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 指定映射配置文件的位置-->
    <mappers>
        <mapper resource="mapper/UserDao.xml"/>
    </mappers>
</configuration>

7.编写测试类

public class UserTest {

    @Test
    public void testUser() throws IOException {
        // 1.读取配置文件
        InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
        // 2.创建 SqlSessionFactory 工厂
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        // 3.获取 SqlSession 对象
        SqlSession session = factory.openSession();
        // 4.使用 SqlSession 创建 Dao 接口的代理对象
        UserDao userDao = session.getMapper(UserDao.class);
        // 5.执行接口的方法
        List<User> userList = userDao.getAll();
        userList.forEach(user ->{
            System.out.println("姓名:"+user.getName()+",性别:"+user.getSex()+",年龄:"+user.getAge());
        });
    }
}

8.执行结果 9.完整目录结构

4. MyBatisUtil

上面测试代码中读取配置文件、获取 sqlsession 对象等都是一些重复性的操作,我们可以将这些代码封装到一个工具类中。

MyBatisUtil:

public class MyBatisUtil {

    // 定义 SqlSessionFactory
    private static SqlSessionFactory factory;

    // 使用静态块只创建一次 SqlSessionFactory
    static {
        try {
            // 读取配置文件
            InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
            // 创建 SqlSessionFactory 对象
            factory = new SqlSessionFactoryBuilder().build(in);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 获取 SqlSession 对象
    public static SqlSession getSqlSession() {
        SqlSession sqlSession = factory.openSession();
        return sqlSession;
    }

    // 提交事务
    public static void commit(SqlSession sqlSession) {
        if (null != sqlSession) {
            sqlSession.commit();
        }
        close();
    }

    // 回滚事务
    public static void rollBack(SqlSession sqlSession) {
        if (null != sqlSession) {
            sqlSession.rollback();
        }
        close();
    }

    // 关闭 SqlSession
    public static void close() {
        SqlSession sqlSession = getSqlSession();
        if (null != sqlSession) {
            sqlSession.close();
        }
    }
}

5. 增删改查

这里我们使用 MyBatisUtil 实现用户的增删改查操作。

UserDao:

public interface UserDao {

    // 获取所有用户信息
    List<User> getAll();

    // 新增用户
    boolean add(User user);

    // 修改用户
    boolean update(User user);

    // 删除用户
    boolean delete(int id);
}

UserDao.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xxl.dao.UserDao">

    <select id="getAll" resultType="com.xxl.model.User">
        select * from user
    </select>

    <insert id="add" parameterType="com.xxl.model.User">
        insert into user(name,sex,age) values(#{name},#{sex},#{age})
    </insert>

    <update id="update">
        update user set name = #{name}, sex = #{sex},age = #  {age} where id = #{id}
    </update>

    <delete id="delete">
        delete from user where id = #{id}
    </delete>
</mapper>

1.查询用户

@Test
public void testGetAll(){

        // 1.获取 SqlSession 对象
        SqlSession session = MyBatisUtil.getSqlSession();
        // 2.使用 SqlSession 创建 Dao 接口的代理对象
        UserDao userDao = session.getMapper(UserDao.class);
        // 3.执行接口的方法
        List<User> userList = userDao.getAll();
        userList.forEach(user ->{
            System.out.println("姓名:"+user.getName()+",性别:"+user.getSex()+",年龄:"+user.getAge());
        });
        // 4.关闭 SqlSession
        MyBatisUtil.close();
}

2.新增用户

@Test
public void add(){
    SqlSession session = null;
    try{
        // 1.获取 SqlSession 对象
        session = MyBatisUtil.getSqlSession();
        // 2.使用 SqlSession 创建 Dao 接口的代理对象
        UserDao userDao = session.getMapper(UserDao.class);
        // 3.执行接口的方法
        User user = new User();
        user.setName("张无忌");
        user.setAge(31);
        user.setSex("男");
        userDao.add(user);
        // 4.提交事务并关闭 SqlSession
        MyBatisUtil.commit(session);
    }catch (Exception e){
        e.printStackTrace();
        // 回滚事务
        MyBatisUtil.rollBack(session);
    }
}

3.修改用户

@Test
public void update(){
    SqlSession session = null;
    try{
        // 1.获取 SqlSession 对象
        session = MyBatisUtil.getSqlSession();
        // 2.使用 SqlSession 创建 Dao 接口的代理对象
        UserDao userDao = session.getMapper(UserDao.class);
        // 3.执行接口的方法
        User user = new User();
        user.setId(3);
        user.setName("张无忌");
        user.setAge(31);
        user.setSex("男");
        userDao.update(user);
        // 4.提交事务并关闭 SqlSession
        MyBatisUtil.commit(session);
    }catch (Exception e){
        e.printStackTrace();
        // 回滚事务
        MyBatisUtil.rollBack(session);
    }
}

4.删除用户

@Test
public void delete(){
    SqlSession session = null;
    try{
        // 1.获取 SqlSession 对象
        session = MyBatisUtil.getSqlSession();
        // 2.使用 SqlSession 创建 Dao 接口的代理对象
        UserDao userDao = session.getMapper(UserDao.class);
        // 3.执行接口的方法
        userDao.delete(3);
        // 4.提交事务并关闭 SqlSession
        MyBatisUtil.commit(session);
    }catch (Exception e){
        e.printStackTrace();
        // 回滚事务
        MyBatisUtil.rollBack(session);
    }
}

6. MyBatist 核心配置文件

mybatis-config.xml 是 mybatis 的主配置文件,所有的配置都在 configuration 标签里面。

它主要包括:定义别名、配置数据源、配置 mapper 文件等。

1.定义别名

前面我们在 mapper 文件里面设置的 resultType 是全路径名。

<select id="getAll" resultType="com.xxl.model.User">
    select * from user
</select>

为了简化代码,MyBatis 允许我们给全路径名起一个简单的别名,一般是实体类的类名。我们可以在 mybatis-config.xml 里面这样配置:

<!--配置别名-->
<typeAliases>
    <package name="com.xxl.model"/>
</typeAliases>

上面 name 的值一般是实体类所在包的全路径,配置完之后我们就可以直接用 user 代替 com.xxl.model.User 了。

<select id="getAll" resultType="user">
    select * from user
</select>

2.配置数据源

我们使用 dataSource 标签配置连接数据库的各种参数,其中 dataSource 的 type 表示使用连接池配置数据源。

<!-- 配置环境 -->
<environments default="mysql">
    <!-- 配置 mysql 的环境-->
    <environment id="mysql">
        <!-- 事务的类型-->
        <transactionManager type="JDBC"></transactionManager>
        <!-- 配置数据源 POOLED:表示使用连接池 -->
        <dataSource type="POOLED">
            <!-- 配置连接数据库的信息 -->
            <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo"/>
            <property name="username" value="root"/>
            <property name="password" value="12345678"/>
        </dataSource>
    </environment>
</environments>

为了便于维护数据库的连接参数,我们一般会将这些参数存放在一个专门的文件中,MyBatis 主配置文件再从这个文件中读取连接数据库的参数数据。

在 resources 目录下面新建 jdbc.properties 文件 在主配置文件使用 properties 标签引入 jdbc.properties 文件

<properties resource="jdbc.properties"/>

修改 dataSource

<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>

3.配置查找 mapper 文件的路径

 <mappers>
      <mapper resource="mapper/UserDao.xml"/>
  </mappers>

4.事务

Mybatis 框架是对 JDBC 的封装,所以 Mybatis 框架的事务控制方式,其实就是 JDBC 的 Connection 对象的 commit(), rollback() 。

 <transactionManager type="JDBC" />

type="JDBC" : 表示使用 JDBC 的事务管理机制。但是 MyBatis 默认将自动提交功能关闭了,改为了手动提交。

所以上面的测试代码中,我们在做完增删改之后还要手动提交事务。

自动提交事务

我们在获取 sqlSession 的时候,只需要将 openSession 的参数设置为 true,就能将 MyBatis 设置为自动提交事务。

// 读取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 创建 SqlSessionFactory 对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 获取 SqlSession 对象
SqlSession sqlSession = factory.openSession(true);

7. mapper 映射文件

我们之前定义完 Dao 接口之后,还要定义一个它的实现类 DaoImpl。 而 Mybatis 框架根据 mapper.xml 文件帮助我们自动为接口生成了一个代理对象。

// 使用 SqlSession 创建 Dao 接口的代理对象
UserDao userDao = session.getMapper(UserDao.class);

然后我们就可以调用接口中的方法了,所以这个 mapper 文件很重要。

1.约定格式

① DAO 的全类名必须和对应的 mapper 文件中的namespace一致。

② DAO 中的方法名称必须和 mapper 文件中增删改查操作的id一致。

③ DAO 中的方法的返回值必须和 mapper 文件中操作的返回值类型一致。

④ DAO 中的方法的参数类型必须和 mapper 文件中的输入参数的类型一致。

注: mapper 文件中的 sql 语句不要加分号,id 不能重复。

2.封装输出结果

① resultType

执行 sql 之后得到 ResultSet 转换后的结果类型,使用类型的完全限定名或别名。如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。resultType 和 resultMap,不能同时使用。

简单类型

接口方法

 String getName(int id);

mapper 文件:

<select id="getName" resultType="java.lang.String">
  select name from user where id = #{id}
</select>

对象类型

接口方法

 User getUser(int id);

mapper 文件:

<select id="getUser" resultType="user">
  select * from user where id = #{id}
</select>

Map 类型

接口方法

@MapKey("id")
Map<String,Object> getUser();

mapper 文件:

<select id="getUser" resultType="java.util.Map">
    select * from user
</select>

② resultMap

当数据库表中的列名和实体类的属性名不一致的时候,我们可以使用 resultMap 定义 sql 的结果和 java 对象属性的映射关系。

例如:

<resultMap id="userMap" type="com.xxl.model.User">
   <id column="s_id" property="id"/>
    <result column="s_name" property="name"/>
    <result column="s_age" property="age"/>
    <result column="s_sex" property="sex"/>
</resultMap>

<select id="getAll" resultMap="userMap">
    select s_id,s_name,s_age,s_sex from user
</select>

3.模糊查询

方式一:{} 中的值必须是 value

<select id="findByName"  resultType="user">
    select * from user where name like '%${value}%'
</select>

方式二

<select id="findByName"  resultType="user">
    select * from user where name like "%"#{name}"%"
</select>

4.添加/修改处理 null 值

Mybatis 在执行添加、修改操作时不允许出现空值,所以在添加修改 时,要想使某个列的值为空,必须添加 jdbcType 属性。

例如:

<insert id="add" >
  insert into user(name,sex,age) values(#{name,jdbcType=VARCHAR},#{sex,jdbcType=INTEGER},#{age,jdbcType=INTEGER})
</insert>

8. MyBatis 传参

在 Mapper 文件中,我们可以使用 #{param} 和 ${param} 获取接口中传递的参数。

#{param} 是使用占位符的方式,${param} 是采用拼接 sql 语句的方式。

使用 $ 有 sql 注入的风险,所以下面的例子中都会采用 #{param} 的方式。

一个参数

#{}:可以以任意的名字获取参数值。

例如:

<select id="getUser">
    select * from user where id = #{uId}
</select>

<delete id="delete">
    delete from user where id = #{user_id}
</delete>

多个参数

1.传递多个参数

传输多个参数,Mybatis会将这些参数放到map集合里面,可以通过 @Param 指定mapper 文件中参数的名字。例如:

接口:

List<Student> getUser(@Param("userName") String name,@Param("userSex") int age);

mapper 文件:

<select id="getUser" resultType="user">
select id,name,sex,age from user where name = #{userName} and age = #{userSex}
</select>

2.传递对象

传输参数为 JavaBean,#{} 可以通过属性名获取属性值。

接口:

// 修改用户
boolean update(User user);

mapper 文件:

<update id="update">
    update user set name = #{name}, sex = #{sex},age = #{age} where id = #{id}
</update>

3.传递 Map

传递的参数为 map 时,mapper 文件使用 # { key } 获取参数值。

例如:

Map<String,Object> data = new HashMap<String,Object>(); 
data.put("userName","知否君");
data.put("userAge",21);

接口:

List<User> getUser(Map<String,Object> map);

mapper 文件:

<select id="getUser" resultType="user">
    select * from user where name = #{userName} and age = #{userAge}
</select>

9. 动态 SQL

动态 SQL 是 MyBatis 强大特性之一,主要用于解决查询条件不确定的情况,它可以极大的简化我们拼装 SQL 的操作。

1.if 标签

If 标签用于完成简单的判断,当标签中 test 的值为 true 时,会将其包含的 SQL 片段拼接到其所在的 SQL 语句中。

语法格式:

<if test="条件"> sql 片段 </if>

例如:

<select id="getUser" resultType="user">
select id,name,age,sex from user
where 1=1
<if test="name != null and name !='' ">
and name = #{name}
</if>
</select>

2.where 标签

上面的例子中如果 name 参数存在,为了保证 sql 语句的正确性,我们不得不添加 where 1=1

Where 标签就是为了解决 sql 语句中 where 关键字以及条件中第一个 and 或者 or 的问题。

例如:

<select id="getUser" resultType="user">
    select id,name,age,sex from user
   <where>
       <if test="name != null and name !='' ">
           and name = #{name}
       </if>
   </where>
</select>

3.trim 标签

trim 标签可以在条件判断完的 sql 语句前后添加或者去掉指定的字符。

  • prefix: 添加前缀
  • prefixOverrides: 去掉前缀
  • suffix: 添加后缀
  • suffixOverrides: 去掉后缀

例如:

<select id="getUser" resultType="user">
    select id,name,age,sex from user
    <trim prefix="where" suffixOverrides="and">
        <if test="name != null and name !='' ">
            name = #{name} and
        </if>
    </trim>
</select>

4.choose(when、otherwise) 标签

choose 标签主要是用于分支判断,类似于 java 中的 switch case,只会满足所有分支中的一个。

例如:

<select id="getUser"  resultType="user">
    select id,name,age,sex from user
    <where>
        <choose>
            <when test="id != null and id !='' ">
                id = #{id}
            </when>
            <when test="name != null and name !='' ">
                name =  #{name}
            </when>
            <otherwise>
                sex = '男'
            </otherwise>
        </choose>
    </where>
</select>

5.set 标签

set 标签主要是用于解决修改操作中 sql 语句中可能多出逗号的问题。

例如:

<update id="update">
    update user
    <set>
        <if test="name != null and name !='' ">
            name = #{name},
        </if>
        <if test="sex != null and sex !='' ">
            sex = #{sex},
        </if>
        <if test="age != null and age !='' ">
            age = #{age}
        </if>
    </set>
    where id = #{id}
</update>

6.foreach 标签

foreach 标签主要用于循环迭代。

  • collection: 要迭代的集合
  • item: 当前从集合中迭代出的元素
  • open: 开始字符
  • close:结束字符
  • separator: 元素与元素之间的分隔符
  • 迭代的是List集合: index表示的当前元素的下标
  • 迭代的Map集合: index表示的当前元素的key

例如:

<select id="getUserList"  resultType="user">
    select id,name,age,sex from user  where  id in
    <foreach collection="ids" item="userId"  open="(" close=")"  separator="," >
        #{userId}
    </foreach>
</select>

7.sql 标签

sql 标签是用于抽取可重用的 sql 片段,将相同的、使用频繁的 sql 片段抽取出来,单独定义,方便多次引用。

例如:

<sql id="BaseSql">
    id,name,age,sex
</sql>

<select id="getUserList" resultType="user">
    select
    <include refid="BaseSql" />
    from user
</select>

10. 注解式开发

通过观察 Mapper 文件我们发现,Mapper 文件的核心就是与 Dao 做接口映射,然后里面写一些 sql 语句。

那我能不能不要 mapper 文件,直接在接口里面写 sql 语句?可以的,MyBatsi 为我们提供了注解式开发。

1.修改主配置文件

虽然不用写 mapper 文件了,但是我们要在接口里面写 sql 语句,你得让 MyBatis 知道吧。所以需要告诉 MyBatis 带有注解的接口在哪里。

<!-- 指定带有注解的 dao 接口所在的位置 -->
<mappers>
   <package name="com.xxl.dao"/>
</mappers>
  1. 注解式开发
public interface UserDao {
    // 获取所有用户信息
    @Select("select * from user")
    List<User> getUser();

    // 新增用户
    @Insert("insert into user(name,sex,age) values(#{name},#{sex},#{age})")
    boolean add(User user);

    // 修改用户
    @Update("update user set name = #{name}, sex = #{sex},age = #{age} where id = #{id}")
    boolean update(User user);

    // 删除用户
    @Delete("delete from user where id = #{id}")
    boolean delete(int id);
}

3.测试

  @Test
  public void testGetAll(){
      // 1.获取 SqlSession 对象
      SqlSession session = MyBatisUtil.getSqlSession();
      // 2.使用 SqlSession 创建 Dao 接口的代理对象
      UserDao userDao = session.getMapper(UserDao.class);
      // 3.执行接口的方法
      List<User> userList = userDao.getUser();
      userList.forEach(user ->{
          System.out.println("姓名:"+user.getName()+",性别:"+user.getSex()+",年龄:"+user.getAge());
      });
      // 4.关闭 SqlSession
      MyBatisUtil.close();
  }

执行结果:

11. 关联关系

一对一

例如:一个人对应一张身份证,一张身份证对应一个人。在 MyBatis 的 mapper 文件中使用 association 处理一对一关系。

创建表:

A 表的一条记录,对应 B 表的一条记录,且 A 的主键作为 B 表的外键。这主要看以哪张表为中心。

下面例子中将用户信息表的主键(id)作为用户表的外键(info_id)。

用户表:user

CREATE TABLE `user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(20) DEFAULT NULL COMMENT '姓名',
  `sex` varchar(1) DEFAULT NULL COMMENT '性别',
  `age` int(11) DEFAULT NULL COMMENT '年龄',
  `info_id` int(11) DEFAULT NULL COMMENT '用户信息id',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
INSERT INTO `mybatis_demo`.`user`(`id`, `name`, `sex`, `age`, `info_id`) VALUES (1, '张三', '男', 18, 1);
INSERT INTO `mybatis_demo`.`user`(`id`, `name`, `sex`, `age`, `info_id`) VALUES (2, '李四', '男', 19, 2);

用户信息表:user_info

CREATE TABLE `user_info` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `number` varchar(20) DEFAULT NULL COMMENT '身份证编号',
  `address` varchar(50) DEFAULT NULL COMMENT '家庭地址',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
INSERT INTO `mybatis_demo`.`user_info`(`id`, `number`, `address`) VALUES (1, '411121200302174025', '上海外滩');
INSERT INTO `mybatis_demo`.`user_info`(`id`, `number`, `address`) VALUES (2, '411121200222154554', '北京三里屯');

用户实体类:User

public class User {
    private int id;
    private String name;
    private String sex;
    private int age;
    // 用户信息属性
    private UserInfo userInfo;

    public UserInfo getUserInfo() {
        return userInfo;
    }

    public void setUserInfo(UserInfo userInfo) {
        this.userInfo = userInfo;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", age=" + age +
                '}';
    }
}

用户信息类:

public class UserInfo {
    private int id;
    private String number;
    private String address;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "UserInfo{" +
                "id=" + id +
                ", number='" + number + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

UserDao:

public interface UserDao {
    /**
     * 获取所有用户信息,包括身份证信息
     * @return
     */
    List<User> getAll();
}

UserDao.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xxl.dao.UserDao">
    
    <resultMap type="com.xxl.model.User" id="userMap">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="age" property="age"/>
        <result column="sex" property="sex"/>
        <!--
            association: 用来处理一对一关系属性封装
            property : 关系属性名
            javaType: 关系属性的类型
         -->
        <association property="userInfo" javaType="com.xxl.model.UserInfo" >
            <id column="id" property="id" />
            <result column="number" property="number"/>
            <result column="address" property="address"/>
        </association>
    </resultMap>

    <select id="getAll" resultMap="userMap">
        select user.id,user.name,user.age,user.sex,
               info.id,info.number,info.address
        from user
        left join user_info info
        on  user.info_id = info.id
    </select>
</mapper>

测试代码:

@Test
  public void testUserGetAll(){
      // 1.获取 SqlSession 对象
      SqlSession session = MyBatisUtil.getSqlSession();
      // 2.使用 SqlSession 创建 Dao 接口的代理对象
      UserDao userDao = session.getMapper(UserDao.class);
      // 3.执行接口的方法
      List<User> userList = userDao.getAll();
      userList.forEach(user ->{
          System.out.println(user+" "+user.getUserInfo());
      });
      // 4.关闭 SqlSession
      MyBatisUtil.close();
  }

测试结果:

一对多

例如:一个部门对应多个员工,一个员工属于一个部门。在 MyBatis 的 mapper 文件中使用 collection 处理一对多关系。

创建表:

A 表的一条记录,对应 B 表的多条记录,且 A 的主键作为 B 表的外键。

下面例子中将部门表的主键(id)作为员工表的外键(dep_id)。

部门表:department

CREATE TABLE `department` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL COMMENT '部门名称',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
INSERT INTO `mybatis_demo`.`department`(`id`, `name`) VALUES (1, '研发部');
INSERT INTO `mybatis_demo`.`department`(`id`, `name`) VALUES (2, '人事部');
INSERT INTO `mybatis_demo`.`department`(`id`, `name`) VALUES (3, '销售部');

员工表:employee

CREATE TABLE `employee` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL COMMENT '员工姓名',
  `age` int(11) DEFAULT NULL COMMENT '员工年龄',
  `sex` varchar(1) DEFAULT NULL COMMENT '员工性别',
  `dep_id` int(11) DEFAULT NULL COMMENT '部门id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4;
INSERT INTO `mybatis_demo`.`employee`(`id`, `name`, `age`, `sex`, `dep_id`) VALUES (1, '张无忌', 21, '男', 1);
INSERT INTO `mybatis_demo`.`employee`(`id`, `name`, `age`, `sex`, `dep_id`) VALUES (2, '周芷若', 19, '女', 2);
INSERT INTO `mybatis_demo`.`employee`(`id`, `name`, `age`, `sex`, `dep_id`) VALUES (3, '赵敏', 19, '女', 3);
INSERT INTO `mybatis_demo`.`employee`(`id`, `name`, `age`, `sex`, `dep_id`) VALUES (4, '小昭', 20, '女', 2);
INSERT INTO `mybatis_demo`.`employee`(`id`, `name`, `age`, `sex`, `dep_id`) VALUES (5, '蛛儿', 19, '女', 1);

部门实体类:

public class Department {
    private String id;
    private String name;
    private List<Employee> employeeList;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Employee> getEmployeeList() {
        return employeeList;
    }

    public void setEmployeeList(List<Employee> employeeList) {
        this.employeeList = employeeList;
    }
    @Override
    public String toString() {
        return "Department{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}

员工实体类:

public class Employee {
    private int id;
    private String name;
    private String sex;
    private int age;
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", age=" + age +
                '}';
    }
}

DepartmentDao:

public interface DepartmentDao {

    List<Department> getAll();
}

DepartmentDao.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xxl.dao.DepartmentDao">

    <resultMap type="com.xxl.model.Department" id="deptMap">
        <id column="depId" property="id"/>
        <result column="depName" property="name"/>
        <!--
            collection 用来处理集合类型的属性  ,用来处理一对多关系
            property:  关系属性名
            javaType:  关系属性类型
            ofType  :  集合中泛型类型:类的全路径名
         -->
        <collection property="employeeList" javaType="list" ofType="com.xxl.model.Employee">
            <id column="empId" property="id"/>
            <result  column="empName" property="name"/>
            <result column="age" property="age"/>
            <result column="sex" property="sex"/>
        </collection>
    </resultMap>

    <select id="getAll" resultMap="deptMap">
        select d.id depId,d.name depName,e.id empId,e.name empName,e.age,e.sex
        from department d
        left join employee e
        on d.id  = e.dep_id
    </select>
</mapper>

测试代码:

@Test
public void testDepartmentGetAll(){
    // 1.获取 SqlSession 对象
    SqlSession session = MyBatisUtil.getSqlSession();
    // 2.使用 SqlSession 创建 Dao 接口的代理对象
    DepartmentDao departmentDao = session.getMapper(DepartmentDao.class);
    // 3.执行接口的方法
    List<Department> departmentList = departmentDao.getAll();
    departmentList.forEach(department ->{
        System.out.println("部门:"+department+" 员工:"+department.getEmployeeList());
    });
    // 4.关闭 SqlSession
    MyBatisUtil.close();
}

测试结果: 多对多

例如:例如一个学生可以有多门课程,一门课程可以属于多个学生。多对多可以理解为是一对多和多对一的组合。要实现多对多,一般都需要有一张中间表(也叫关联表),形成多对多的形式。

创建表:

学生表:student

CREATE TABLE `student` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(20) DEFAULT NULL COMMENT '姓名',
  `sex` varchar(1) DEFAULT NULL COMMENT '性别',
  `age` int(11) DEFAULT NULL COMMENT '年龄',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
INSERT INTO `mybatis_demo`.`student`(`id`, `name`, `sex`, `age`) VALUES (1, '张三', '男', 18);
INSERT INTO `mybatis_demo`.`student`(`id`, `name`, `sex`, `age`) VALUES (2, '李四', '女', 21);
INSERT INTO `mybatis_demo`.`student`(`id`, `name`, `sex`, `age`) VALUES (3, '王五', '男', 19);

课程表:course

CREATE TABLE `course` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL COMMENT '课程名称',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;
INSERT INTO `mybatis_demo`.`course`(`id`, `name`) VALUES (1, 'java开发');
INSERT INTO `mybatis_demo`.`course`(`id`, `name`) VALUES (2, '数据结构');
INSERT INTO `mybatis_demo`.`course`(`id`, `name`) VALUES (3, '大数据开发');
INSERT INTO `mybatis_demo`.`course`(`id`, `name`) VALUES (4, '云原生开发');

学生-课程关联表:student_course

CREATE TABLE `student_course` (
  `student_id` int(11) NOT NULL,
  `course_id` int(11) NOT NULL,
  PRIMARY KEY (`student_id`,`course_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `mybatis_demo`.`student_course`(`student_id`, `course_id`) VALUES (1, 2);
INSERT INTO `mybatis_demo`.`student_course`(`student_id`, `course_id`) VALUES (1, 3);
INSERT INTO `mybatis_demo`.`student_course`(`student_id`, `course_id`) VALUES (2, 1);
INSERT INTO `mybatis_demo`.`student_course`(`student_id`, `course_id`) VALUES (2, 2);
INSERT INTO `mybatis_demo`.`student_course`(`student_id`, `course_id`) VALUES (3, 3);
INSERT INTO `mybatis_demo`.`student_course`(`student_id`, `course_id`) VALUES (3, 4);

学生实体类:

public class Student {
    private int id;
    private String name;
    private String sex;
    private int age;
    private List<Course> courseList;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public List<Course> getCourseList() {
        return courseList;
    }

    public void setCourseList(List<Course> courseList) {
        this.courseList = courseList;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", age=" + age +
                '}';
    }
}

课程实体类:

public class Course {
    private int id;
    private String name;
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Course{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

StudentDao:

public interface StudentDao {

   List<Student> getAll();
}

StudentDao.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xxl.dao.StudentDao">

    <resultMap type="com.xxl.model.Student" id="studentMap">
        <id column="studentId" property="id"/>
        <result column="studentName" property="name"/>
        <result column="age" property="age"/>
        <result column="sex" property="sex"/>
        <!--
          collection 用来处理集合类型的属性  ,用来处理一对多关系
          property:  关系属性名
          javaType:  关系属性类型
          ofType  :  集合中泛型类型:类的全路径名
       -->
        <collection property="courseList" javaType="list" ofType="com.xxl.model.Course">
            <id column="courseId" property="id"/>
            <result column="courseName" property="name"/>
        </collection>
    </resultMap>

    <select id="getAll" resultMap="studentMap">
         select s.id studentId,s.name studentName,s.age,s.sex,
                c.id courseId,c.name courseName
         from    student s
         left join student_course sc
         on s.id = sc.student_id
         left join course c
         on sc.course_id = c.id
    </select>
</mapper>

测试代码:

@Test
  public void testStudentGetAll(){
      // 1.获取 SqlSession 对象
      SqlSession session = MyBatisUtil.getSqlSession();
      // 2.使用 SqlSession 创建 Dao 接口的代理对象
      StudentDao studentDao = session.getMapper(StudentDao.class);
      // 3.执行接口的方法
      List<Student> studentList = studentDao.getAll();
      studentList.forEach(student ->{
          System.out.println("学生:"+student+" 课程:"+student.getCourseList());
      });
      // 4.关闭 SqlSession
      MyBatisUtil.close();
  }

测试结果:

12. 分页插件

我们之前在写分页的时候,不仅要自定义一个 Page 类,还要拼接 sql 语句,最后还要封装数据。真的是”恶心他妈妈给恶心开门——恶心到家了。“

为了解决这个问题,MyBatis 为我们提供了一个通用的分页工具:PageHelper。

使用步骤:

1.引入依赖

<!-- 分页插件 -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.4</version>
</dependency>

2.修改主配置文件

在 environments 标签之前添加:

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor" />
</plugins>

3.准备数据 4.测试代码

  @Test
  public void testGetAll(){
      // 1.获取 SqlSession 对象
      SqlSession session = MyBatisUtil.getSqlSession();
      // 2.使用 SqlSession 创建 Dao 接口的代理对象
      UserDao userDao = session.getMapper(UserDao.class);
      // 3.执行接口的方法
      Page page = PageHelper.startPage(2, 3);
      List<User> userList = userDao.getUser();
      System.out.println("当前页:"+page.getPageNum());
      System.out.println("每页条数:"+page.getPageSize());
      System.out.println("总条数:"+page.getTotal());
      System.out.println("总页数:"+page.getPages());
      System.out.println("-------------------------");
      userList.forEach(user ->{
          System.out.println("姓名:"+user.getName()+",性别:"+user.getSex()+",年龄:"+user.getAge());
      });
      // 4.关闭 SqlSession
      MyBatisUtil.close();
  }

5.执行结果