MyBatis:基本应用

框架简介

SSM = Spring MVC + Spring + MyBatis

三层架构

软件开发常用的架构是三层架构,之所以流行是因为有着清晰的任务划分。一般包括以下三层:

  • 持久层:主要完成与数据库相关的操作,即对数据库的增删改查。因为数据库访问的对象一般称为 Data Access Object(简称 DAO),所以有人把持久层叫做 DAO 层。
  • 业务层:主要根据功能需求完成业务逻辑的定义和实现。因为它主要是为上层提供服务的,所以有人把业务层叫做 Service 层或 Business 层。
  • 表现层:主要完成与最终软件使用用户的交互,需要有交互界面(UI)。因此,有人把表现层称之为 web 层或 View 层。

三层架构之间调用关系为:表现层调用业务层,业务层调用持久层。

各层之间必然要进行数据交互,一般使用 java 实体对象来传递数据。

业务层的业务逻辑是开发的难点所在。

框架
什么是框架?

框架就是一套规范,既然是规范,你使用这个框架就要遵守这个框架所规定的约束。

框架可以理解为半成品软件,框架做好以后,接下来在它基础上进行开发。

为什么使用框架?

框架封装好了一些冗余、重用率低的代码,并且使用反射与动态代理机制,将代码实现了通用性,让开发人员把精力专注在核心的业务代码实现上。

比如,在使用 Servlet 进行开发时,需要在 Servlet 获取表单的参数,每次都要获取很麻烦,而框架底层就使用反射机制和拦截器机制获取表单的值,使用 JDBC 每次做一些简单的 CRUD 的时候都必须写 SQL,但使用框架就不需要这么麻烦了,直接调用方法就可以。当然,既然是使用框架,那么还是要遵循其一些规范进行配置。

常见的框架

Java 框架非常的多,每一个框架都是为了解决某一部分或某些问题而存在的。下面列出在目前企业中流行的几种框架:

  • 表现层框架:专注于解决与用户交互的框架,常见的有 Struts 2、Spring MVC 等等。
  • 全栈框架:能在各层都给出解决方案的框架,如 Spring。
  • 持久层框架:专注于解决数据持久化的框架,常用的有 MyBatis(半自动,需要手动写 SQL)、Hibernate(全自动)、Spring JDBC 等等。

几年前流行的框架组合:Struts 2 + Spring + Hibernate(SSH)。

目前企业中最常用的组合是: Spring MVC + Spring + MyBatis(SSM)。

MyBatis 简介

MyBatis 是一个优秀的基于 ORM 的半自动轻量级持久层框架,它对 JDBC 的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建 connection、创建 statement、手动设置参数、结果集检索等 JDBC 繁杂的过程代码。

程序在启动时消耗的资源多少决定了它是轻量级还是重量级。

原始 JDBC 操作的分析
  1. 数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能。
  2. SQL 语句在代码中硬编码,造成代码不易维护,实际应用 SQL 变化的可能较大,SQL 变动需要改变 Java 代码。
  3. 查询操作时,需要手动将结果集中的数据手动封装到实体中。

解决方案:

  1. 使用数据库连接池初始化连接资源。
  2. 将 SQL 语句抽取到 XML 配置文件中。
  3. 使用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射。
MyBatis 历史

MyBatis 本是 apache 的一个开源项目 iBatis,2010 年 6 月这个项目由 apache software foundation 迁移到了 google code,随着开发团队转投到 Google Code 旗下,iBatis 正式改名为 MyBatis,代码于 2013 年 11 月迁移到 GitHub。

GitHub 地址:https://github.com/mybatis/mybatis-3/

ORM 思想

ORM(Object Relational Mapping)对象关系映射

  • O(对象模型):实体对象,即我们在程序中根据数据库表结构建立的一个个实体 Java Bean
  • R(关系型数据库的数据结构):数据库表,关系型数据库的结构模型
  • M(映射):从 R(数据库)到 O(对象模型)的映射,可通过 XML 文件映射

实现:

  1. 让实体类和数据库表进行一一对应关系:先让实体类和数据库表对应,再让实体类属性和表里面字段对应。
  2. 不需要直接操作数据库表,直接操作表对应的实体类对象。

ORM 作为是一种思想帮助跟踪实体的变化,并将实体的变化翻译成 SQL 脚本,执行到数据库中去,也就是将实体的变化映射到了表的变化。

MyBatis 采用 ORM 思想解决了实体和数据库映射的问题,对 JDBC 进行了封装,屏蔽了 JDBC API 底层访问细节,使我们不用与 JDBC API 打交道就可以完成对数据库的持久化操作。

MyBatis 快速入门

MyBatis 开发步骤

MyBatis 学习文档:https://mybatis.org/mybatis-3/

案例需求:通过 MyBatis 查询数据库 user 表的所有记录,封装到 User 对象中,打印到控制台上

1. 创建数据库及 user 表

2. 创建 maven 工程,导入依赖(MySQL 驱动、mybatis、junit)

3. 编写 User 实体类

4. 编写 UserMapper.xml 映射配置文件(ORM思想)

5. 编写 SqlMapConfig.xml 核心配置文件
       数据库环境配置
       映射关系配置的引入 (引入映射配置文件的路径)

6. 编写测试代码   
    // 1.加载核心配置文件
    // 2.获取 sqlSessionFactory 工厂对象
    // 3.获取 sqlSession 会话对象
    // 4.执行 sql
    // 5.打印结果
    // 6.释放资源
代码实现
创建 user 数据表
CREATE DATABASE `mybatis_db`;

USE `mybatis_db`;

CREATE TABLE `user` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `username` VARCHAR(32) NOT NULL COMMENT '用户名称',
  `birthday` DATETIME DEFAULT NULL COMMENT '生日',
  `sex` CHAR(1) DEFAULT NULL COMMENT '性别',
  `address` VARCHAR(256) DEFAULT NULL COMMENT '地址',
  PRIMARY KEY  (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO `user` (`id`,`username`,`birthday`,`sex`,`address`) 
VALUES
(1,'子慕','2020-11-11 00:00:00','男','北京海淀'),
(2,'应颠','2020-12-12 00:00:00','男','北京海淀');
创建 Maven 项目,在 pom.xml 导入 MyBatis 的坐标和其他相关坐标
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.renda</groupId>
    <artifactId>mybatis_quickstart</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- 指定编码及版本 -->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
        <java.version>1.11</java.version>
        <maven.compiler.source>1.11</maven.compiler.source>
        <maven.compiler.target>1.11</maven.compiler.target>
    </properties>

    <!-- 引入相关依赖 -->
    <dependencies>
        <!-- 引入 mybatis 依赖 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.4</version>
        </dependency>

        <!-- 引入 mysql 驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
            <scope>runtime</scope>
        </dependency>

        <!-- 引入 junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>
编写 User 实体
public class User {
    
    
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
	// 省略 getter 和 setter 
}
编写 UserMapper 映射文件

资源文件夹 resourses/mapper 中添加 UserMapper.xml

namespace:命名空间 与 id 属性共同构成唯一标识 namespace.id - user.findAll

resultType:返回结果类型(自动映射封装),要封装的实体的全路径

<?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="userMapper">

    <!-- 查询所有 -->
    <select id="findAll" resultType="com.renda.domain.User">
        select * from `user`
    </select>

</mapper>
编写 MyBatis 核心文件

资源文件夹 resourses 中添加 sqlMapConfig.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>

    <!-- environments: 运行环境 -->
    <environments default="development">
        <environment id="development">
            <!-- 当前的事务事务管理器是 JDBC -->
            <transactionManager type="JDBC"/>
            <!-- 数据源信息 POOLED:使用 mybatis 的连接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///mybatis_db?characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="password"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 引入映射配置文件 -->
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>

</configuration>
编写测试类
public class MyBatisTest {
    
    
    /**
     * 快速入门测试方法
     */
    @Test
    public void myBatisQuickStart() throws IOException {
    
    
        // 1.加载核心配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");

        // 2.获取 sqlSessionFactory 工厂对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

        // 3.获取 sqlSession 会话对象
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 4.执行 sql,参数 statement id
        List<User> users = sqlSession.selectList("UserMapper.findAll");

        // 5.遍历打印结果
        for (User user : users) {
    
    
            System.out.println(user);
        }

        // 6.释放资源
        sqlSession.close();
    }
}
步骤总结
  1. 创建 mybatis_db 数据库和 user 表
  2. 创建项目,导入依赖
  3. 创建 User 实体类
  4. 编写映射文件 UserMapper.xml
  5. 编写核心文件 SqlMapConfig.xml
  6. 编写测试类

MyBatis 增删改查

新增
编写映射文件 UserMapper.xml
<!-- 新增用户 -->
<!-- #{} : mybatis 中的占位符,等同于 JDBC 中的 parameterType :指定接收到的参数类型 -->
<insert id="saveUser" parameterType="com.renda.domain.User">
    insert into `user` (username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address})
</insert>
编写测试类
@Test
public void testSave() throws IOException {
    
    
    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    SqlSession sqlSession = sqlSessionFactory.openSession(true);

    User user = new User();
    user.setUsername("Renda");
    user.setBirthday(new Date());
    user.setSex("M");
    user.setAddress("Guangzhou");

    if (sqlSession.insert("userMapper.saveUser", user) > 0) {
    
    
        System.out.println("新增成功");
    } else {
    
    
        System.out.println("新增失败");
    }

    // 手动提交事务
    // sqlSession.commit();
    sqlSession.close();
}
新增注意事项
  • 插入语句使用 insert 标签
  • 在映射文件中使用 parameterType 属性指定要插入的数据类型
  • SQL 语句中使用 #{实体属性名} 方式引用实体中的属性值,#{} 表示一个占位符号,可以实现 preparedStatement 向占位符中设置值,自动进行 Java 和 JDBC 类型的转换;${} 表示拼接 SQL 字符串,不进行 JDBC 类型转换
  • 插入操作使用的 API 是 sqlSession.insert(“命名空间.id”, 实体对象);
  • 插入操作涉及数据库数据变化,所以要使用 sqlSession 对象显示的提交事务,即 sqlSession.commit()
修改
编写映射文件 UserMapper.xml
<!-- 更新用户 -->
<update id="updateUser" parameterType="com.renda.domain.User">
    update `user` set username = #{username},birthday = #{birthday},sex = #{sex},address = #{address} where id = #{id}
</update>
编写测试类
@Test
public void testUpdate() throws IOException {
    
    
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("sqlMapConfig.xml"));
    SqlSession sqlSession = sqlSessionFactory.openSession();

    User user = new User();
    user.setId(4);
    user.setUsername("lucy");
    user.setBirthday(new Date());
    user.setSex("女");
    user.setAddress("北京朝阳");

    if (sqlSession.update("userMapper.updateUser", user) > 0) {
    
    
        System.out.println("更新用户成功");
    } else {
    
    
        System.out.println("更新用户失败");
    }

    // 手动提交事务
    sqlSession.commit();
    sqlSession.close();
}
修改注意事项
  • 修改语句使用 update 标签
  • 修改操作使用的 API 是 sqlSession.update(“命名空间.id”,实体对象);
删除
编写映射文件 UserMapper.xml
<!-- 删除用户 java.lang.Integer -->
<delete id="deleteUser" parameterType="java.lang.Integer">
    delete from `user` where id = #{abc}
</delete>
编写测试类
/**
 * 测试删除用户
 */
@Test
public void testDelete() throws IOException {
    
    
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("sqlMapConfig.xml"));
    SqlSession sqlSession = sqlSessionFactory.openSession();

    if (sqlSession.delete("userMapper.deleteUser",4) > 0) {
    
    
        System.out.println("删除成功");
    } else {
    
    
        System.out.println("删除失败");
    }

    // 手动提交事务
    sqlSession.commit();
    sqlSession.close();
}
删除注意事项
  • 删除语句使用 delete 标签
  • SQL 语句中使用 #{任意字符串} 方式引用传递的单个参数
  • 删除操作使用的 API 是 sqlSession.delete(“命名空间.id”,Object);

MyBatis 核心文件概述

MyBatis 核心配置文件层级关系

MyBatis 的配置文件包含了会影响 MyBatis 行为的设置和属性信息;配置文档的顶层结构如下:

> configuration 配置
    - properties 属性
    - settings 设置
    - typeAliases 类型别名
    - typeHandlers 类型处理器
    - objectFactory 对象工厂
    - plugins 插件
    - environments 环境配置
        ~ environment 环境变量
            + transactionManager 事务管理器
            + dataSource 数据源
    - databaseIdProvider 数据库厂商标识
    - mappers 映射器
MyBatis 常用配置解析
environments 标签

数据库环境的配置,支持多环境配置

1. 事务管理器(transactionManager)类型有两种:

   - JDBC:
     这个配置就是直接使用了JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作域。

   - MANAGED:
     这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期。
     例如:mybatis 与 spring 整合后,事务交给 spring 容器管理。


2. 数据源(dataSource)常用类型有三种:

   - UNPOOLED:
     这个数据源的实现只是每次被请求时打开和关闭连接。

   - POOLED:
     这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来。
     
   - JNDI : 
     这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,
     容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用
properties 标签

实际开发中,习惯将数据源的配置信息单独抽取成一个 properties 文件,该标签可以加载额外配置的 properties。

在 resources 目录下创建 jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///mybatis_db?characterEncoding=utf8
jdbc.username=root
jdbc.password=password

引入文件:

<!-- 加载 properties 文件 -->
<properties resource="jdbc.properties"></properties>

<!-- environments: 运行环境 -->
<environments default="development">
    <environment id="development">
        <!-- 当前的事务事务管理器是 JDBC -->
        <transactionManager type="JDBC"/>
        <!-- 数据源信息 POOLED:使用 mybatis 的连接池 -->
        <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>
typeAliases 标签

类型别名是为 Java 类型设置一个短的名字。

为了简化映射文件 Java 类型设置,MyBatis 框架设置好的一些常用的类型的别名:

string - String 数据类型

long - Long 数据类型

int - Integer 数据类型

double - Double 数据类型

boolean - Boolean 数据类型

sqlMapConfig.xml 配置 typeAliases,为 com.lagou.domain.User 定义别名为 user

<!-- 设置别名 -->
<typeAliases>
    <!-- 方式一:给单个实体起别名 -->
    <!-- <typeAlias type="com.renda.domain.User" alias="user"></typeAlias> -->
    <!-- 方式二:批量起别名 别名就是类名,且不区分大小写 -->
    <package name="com.renda.domain"/>
</typeAliases>

UserMapper.xml 中使用别名

<!-- 查询所有 -->
<select id="findAll" resultType="user">
    select * from `user`
</select>

<!-- 新增用户 -->
<insert id="saveUser" parameterType="user">
    insert into `user` (username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address})
</insert>

<!-- 更新用户 -->
<update id="updateUser" parameterType="user">
    update `user` set username = #{username},birthday = #{birthday},sex = #{sex},address = #{address} where id = #{id}
</update>

<!-- 删除用户 java.lang.Integer -->
<delete id="deleteUser" parameterType="int">
    delete from `user` where id = #{abc}
</delete>
mappers 标签

该标签的作用是加载映射的,加载方式有如下几种:

  1. 使用相对于类路径的资源引用,例如 <mapper resource="org/mybatis/builder/userMapper.xml"/>
  2. 使用完全限定资源定位符(URL),例如 <mapper url="file:///var/mappers/userMapper.xml"/>
  3. 使用映射器接口实现类的完全限定类名,例如 <mapper class="org.mybatis.builder.userMapper"/>
  4. 将包内的映射器接口实现全部注册为映射器,例如 <package name="org.mybatis.builder"/>

其中 3 和 4 在 mapper 代理开发中使用。

MyBatis 的 API 概述

API 介绍
SqlSession 工厂构建器 SqlSessionFactoryBuilder

常用 API:SqlSessionFactory build(InputStream inputStream)

通过加载 MyBatis 的核心文件的输入流的形式构建一个 SqlSessionFactory 对象

String resource = "org/mybatis/builder/mybatis-config.xml"; 
InputStream inputStream = Resources.getResourceAsStream(resource); 
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); 
SqlSessionFactory factory = builder.build(inputStream);

其中, Resources 工具类在 org.apache.ibatis.io 包中。Resources 类从类路径下、文件系统或一个 web URL 中加载资源文件。

SqlSession 工厂对象 SqlSessionFactory

SqlSessionFactory 有多个个方法创建 SqlSession 实例。常用的有如下两个:

  • openSession() - 会默认开启一个事务,但事务不会自动提交,也就意味着需要手动提交该事务,更新操作数据才会持久化到数据库中
  • openSession(boolean autoCommit) - 参数为是否自动提交,如果设置为 true,那么不需要手动提交事务
SqlSession 会话对象

SqlSession 实例在 MyBatis 中是非常强大的一个类,拥有所有执行语句、提交或回滚事务和获取映射器实例的方法。

执行语句的方法主要有:

<T> T selectOne(String statement, Object parameter) 
<E> List<E> selectList(String statement, Object parameter) 
int insert(String statement, Object parameter) 
int update(String statement, Object parameter) 
int delete(String statement, Object parameter)

操作事务的方法主要有:

void commit()  
void rollback() 
MyBatis 基本原理介绍

配置文件:

  • 核心配置文件 SqlMapConfig.xml - 数据源、事务、引入映射配置文件

  • 映射配置文件 xxxMapper.xml - 主要配置了要执行的 SQL 语句,传入参数,传出参数等等

SqlSessionFactoryBuilder

  • 读取配置文件,将配置文件转化为 Java 对象
  • 使用配置文件中的信息创建了 sqlSessionFactory 工厂对象 - new SqlSessionFactoryBuilder().build(in)

SqlSessionFactory 会话工厂对象:

  • 进行了初始化配置 - 使用 dom4j 解析了配置文件,将映射配置文件中的配置封装了一个一个的 MappedStatement 对象,再将这些对象封装成 Map 集合
  • 默认实现类是 DefaultSqlSessionFactory
  • 创建 SqlSession 会话对象 - sqlSessionFactory.openSession()

SqlSession 会话对象:

  • 这是一个面向程序员的接口,基于它实现增删改查
  • 默认的实现类是 DefaultSqlSession
  • 本身不会直接操作数据库,委派给 Executor 执行器来操作 - sqlSession.selectList(userMapper.findAll)

Executor 执行器:

  • 该接口有两个实现 - 基本执行器、缓存执行器(默认)
  • 获取 MappedStatement,执行 JDBC 代码

MappedStatement

  • 用于封装 statement 时所需的入参、SQL 语句
  • 用于将数据库返回结果封装成指定的返回类型

MyBatis 的 Dao 层开发使用

传统开发方式
编写 UserMapper 接口
public interface IUserDao {
    
    
    public List<User> findAll() throws IOException;
}
编写 UserMapper 实现
public class UserDaoImpl implements IUserDao {
    
    
    @Override
    public List<User> findAll() throws IOException {
    
    
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        List<User> list = sqlSession.selectList("userMapper.findAll");

        sqlSession.close();
        return list;
    }
}
编写 UserMapper.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="userMapper">

    <!-- 查询所有 -->
    <select id="findAll" resultType="user">
        select * from `user`
    </select>

</mapper>
测试
@Test
public void testFindAll() throws IOException {
    
    
    // 创建 UserMapper 实现类
    IUserDao userDao = new UserDaoImpl();

    // 执行查询
    List<User> list = userDao.findAll();
    list.forEach(System.out::println);
}
传统方式问题思考
  1. 实现类中,存在 MyBatis 模板代码重复

  2. 实现类调用方法时,xml 中的 Statement ID 硬编码到 java 代码中

能否只写接口,不写实现类,即只编写接口和 Mapper.xml。

在 Dao(mapper)的实现类中对 SqlSession 的使用方式很类似,因此 MyBatis 提供了接口的动态代理。

代理开发方式
介绍

采用 MyBatis 的基于接口代理方式实现持久层的开发,是目前企业的主流。

基于接口代理方式的开发只需要程序员编写 Mapper 接口,MyBatis 框架会动态生成实现类的对象。

这种开发方式要求我们遵循一定的规范:

  • Mapper.xml 映射文件中的 namespace 与 mapper 接口的全限定名相同
  • Mapper 接口方法名和 Mapper.xml 映射文件中定义的每个 statement 的 id 相同
  • Mapper 接口方法的输入参数类型和 mapper.xml 映射文件中定义的每个 SQL 的 parameterType 的类型相同
  • Mapper 接口方法的输出参数类型和 mapper.xml 映射文件中定义的每个 SQL 的 resultType 的类型相同

Mapper 接口开发方法只需要程序员编写 Mapper 接口(相当于 Dao 接口),由 MyBatis 框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边 Dao 接口实现类方法。

编写 UserMapper 接口
public interface UserMapper {
    
    

    /**
     * 根据 id 查询用户
     */
    public User findUserById(int id);

}
编写 UserMapper.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.renda.mapper.UserMapper">

    <!--根据id查询用户-->
    <select id="findUserById" parameterType="int" resultType="user">
        select * from `user` where id = #{id}
    </select>

</mapper>

sqlMapConfig.xml 中引入映射配置文件

<mappers>
    <!-- 使用映射器接口实现类的完全限定类名方式 -->
    <!-- <mapper class="com.renda.mapper.UserMapper"/> -->
    <!-- 将包内的映射器接口实现全部注册为映射器 -->
    <package name="com.renda.mapper"/>
</mappers>
测试
@Test
public void testFindAll() throws IOException {
    
    
    // 下面几句的模板代码已经不需要在 Dao 层编写,后续与 Spring 整合后就不需要重复写模板代码了
    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 当前返回的其实是基于 UserMapper 所产生的代理对象:底层 - JDK 动态代理,实际类型 - proxy
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.findUserById(1);
    System.out.println(user);
}
MyBatis 基于接口代理方式的内部执行原理

持久层现在只有一个接口,而接口是不实际干活的。

UserMapper mapper = sqlSession.getMapper(UserMapper.class);

通过追踪源码会发现 mapper 实际上是一个代理对象,是由 MapperProxy 代理产生的。

@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
    
    
    // 底层基于 JDK 动态代理产生的代理对象
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] {
    
     mapperInterface }, mapperProxy);
}

MapperProxy 实现了 InvocationHandler 接口,并实现了 invoke 方法。

代理对象调用接口中任意方式时,底层 MapperProxyinvoke 方法都会执行:

@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
    
    
    return mapperMethod.execute(sqlSession, args);
}

MapperProxy 代理的内部类重写的 invoke 方法调用了 MapperMethodexecute 方法:

public Object execute(SqlSession sqlSession, Object[] args) {
    
    
    Object result;
    switch (command.getType()) {
    
    
        case INSERT: {
    
    
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.insert(command.getName(), param));
            break;
        }
        case UPDATE: {
    
    
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.update(command.getName(), param));
            break;
        }
        case DELETE: {
    
    
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.delete(command.getName(), param));
            break;
        }
        case SELECT:
            if (method.returnsVoid() && method.hasResultHandler()) {
    
    
                executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (method.returnsMany()) {
    
    
                result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) {
    
    
                result = executeForMap(sqlSession, args);
            } else if (method.returnsCursor()) {
    
    
                result = executeForCursor(sqlSession, args);
            } else {
    
    
                Object param = method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(command.getName(), param);
                if (method.returnsOptional()
                    && (result == null || !method.getReturnType().equals(result.getClass()))) {
    
    
                    result = Optional.ofNullable(result);
                }
            }
            break;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
    
    
        throw new BindingException("Mapper method '" + command.getName()
                                   + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
}

最后由 execute 方法会发现最终工作的还是 sqlSession

想了解更多,欢迎关注我的微信公众号:Renda_Zhang

猜你喜欢

转载自blog.csdn.net/qq_40286307/article/details/108410528
今日推荐