SSM框架-MyBatis

MyBatis

MyBatis 是当前主流的 Java 持久层框架之一,它与 Hibernate 一样,也是一种 ORM 框架。因其性能优异,且具有高度的灵活性、可优化性和易于维护等特点,所以受到了广大互联网企业的青睐,是目前大型互联网项目的首选框架。

1.MyBatis核心组件介绍

MyBatis简介

MyBatis 是一个支持普通 SQL 查询、存储过程以及高级映射的持久层框架;使用简单的 XML 或注解进行配置和原始映射,用以将接口和 Java 的 POJO(普通 Java 对象)映射成数据库中的记录,使得 Java 开发人员可以使用面向对象的编程思想来操作数据库。

MyBatis 工作原理

MyBatis 框架在操作数据库时,大体经过了8个步骤,如图1所示:

图1 MyBit 框架执行流程图

  1. 读取 MyBatis 配置文件 mybatis-config.xml。该文件配置了 MyBatis 的运行环境等信息(获取数据库连接);
  2. 加载映射文件 Mapper.xml。该文件配置了操作数据库的SQL语句。需在 mybatis-config.xml 中加载才能执行,可加载多个配置文件,每个配置文件对应数据库中的一张表;
  3. 构建会话工厂。通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory;
  4. 创建 SqlSession 对象。由会话工厂创建 SqlSession对 象,该对象中包含了执行SQL的所有方法;
  5. MyBatis 底层定义了一个 Executor 接口来操作数据库,其根据 SqlSession 传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护;
  6. 在 Executor 接口的执行方法中,包含一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等。Mapper.xml 文件中一个 SQL 对应一个 MappedStatement 对象, SQL 的 id 即是 MappedStatement 的 id;
  7. 输入参数映射。在执行方法时,Executor执行器会通过 MappedStatement 对象在执行SQL前,将输入的 Java 对象映射到SQL语句中;
  8. 输出结果映射。在数据库中执行完 SQL 语句后,Executor 执行器会通过 MappedStatement 对象在执行 SQL 语句后,将输出结果映射至 Java 对象中;

MyBatis 核心对象

在使用 MyBatis 框架时,主要涉及两个核心对象 SqlSessionFactorySqlSession,它们在 MyBatis 框架中起着至关重要的作用。

  • SqlSessionFactory

    它是单个数据库映射关系经过编译后的内存镜像,其主要作用是创建 SqlSession。SqlSessionFactoryBuilder 通过 XML 配置文件或一个预先定义好的 Configuration 实例来构建 SqlSessionFactory 实例。其实例是线程安全的。示例代码如下:

    1
    2
    3
    4
    // 读取配置文件
    InputStream inputStream = Resources.getResourceAsStream("配置文件位置");
    // 根据配置文件构建SqlSessionFactory
    SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(inputStream);
  • SqlSession

    它是应用程序与持久层之间执行交互操作的一个单线程对象,其主要作用是执行持久化操作。SqlSession 对象包含了数据库中所有执行 SQL 操作的方法。其底层封装了 JDBC 连接,可直接使用其实例来执行已映射的 SQL 语句。其常用方法如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // statement参数是配置文件中定义的<select>、<insert>、<update>和<delete>元素的id
    // 单个查询
    <T>T selectOne(String statement);
    <T>T selectOne(String statement,Object parameter);
    // 列表查询
    <E>List<E> selectList(String statment);
    <E>List<E> selectList(String statment, Object parameter);
    <E>List<E> selectList(String statment, Object parameter, RowBounds rowBounds);
    // 插入
    int insert(String statement);
    int insert(String statement, Object parameter);
    // 更新
    int update(String stetement);
    int update(String statement, Object parameter);
    // 删除
    int delete(String statement);
    int delete(String statement, Object prameter);

任务代码

TestMybatis.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.educode.test;
import com.educode.model.Customer;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import java.io.IOException;
import java.io.InputStream;
public class TestMybatis {

public static void main(String[] args) throws IOException {
init();
// 1. 读取配置文件
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = null;
// 2. 根据配置文件构建SqlSessionFactory,并赋值给sqlSessionFactory变量
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 通过sqlSessionFactory创建sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4. 新增客户,sqlSession执行映射文件中定义的SQL,并返回映射结果
Customer newCustomer = new Customer("lisi", "teacher", "1888888888");
int rows = sqlSession.insert("addCustomer", newCustomer);
if (rows > 0) {
System.out.println("新增客户成功!");
}
Customer customer = null;
/********* Begin *********/
// 5. 查询客户,sqlSession执行映射文件中定义的SQL,并将返回映射结果赋值给customer变量
customer = sqlSession.selectOne("findCustomerByName", "lisi");
/********* End *********/
System.out.println(customer);
// 6. 提交事务
sqlSession.commit();
// 7. 关闭sqlSession
sqlSession.close();
}
public static void init() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取 jdbcTemplate 实例
JdbcTemplate jdbcTemplate = (JdbcTemplate) applicationContext.getBean("jdbcTemplate");
try {
jdbcTemplate.execute("DROP TABLE customer");
} catch (Exception e) {
}
// 定义创建客户表SQL
String createCustomerSQL = "CREATE TABLE customer(" +
"id INT PRIMARY KEY auto_increment," +
"name VARCHAR(20)," +
"jobs VARCHAR(50)," +
"phone VARCHAR(50))";
jdbcTemplate.execute(createCustomerSQL);
}
}

2.MyBatis配置文件

MyBatis 的核心配置文件中,包含了很多影响 MyBatis行为的重要信息。其中 configuration 元素是配置文件的根元素,其它元素都要在该元素内配置;

properties

该元素通常用于将内部的配置外在化,即通过外部的配置来动态地替换内部定义的属性。例如,数据库的连接等属性,就可以通过典型的 Java 属性文件中的配置来替换。示例如下:

1
2
// 引入 db.properties 配置文件中的属性
<properties resource="db.properties"/>

settings

该元素主要用于改变 MyBatis 运行时的行为,例如开启二级缓存、开启延迟加载等。

设置参数 描述 有效值
cacheEnable 是否开启全局缓存 true|false(默认)
lazyLoadingEnable 是否开启全局延迟加载 true|false(默认)
multipleResultSetsEnable 是否允许单一语句返回多结果集 true(默认)|false
useColumnLabel 使用列标签代替列名 true(默认)|false
useGeneratedKeys 允许 JDBC 支持自动生成主键 true|false(默认)
defaultStatementTimeout 配置默认执行器 SIMPLE(默认)、REUSE(重用预处理语句)、BATCH(重用语句并执行批量执行)
mapUnderscoreToCamelCase 是否开启自动驼峰命名规则映射 true|false(默认)
jdbcTypeForNull 当参数没有指定 JDBC 类型时,设置默认 JDBC 类型 NULL、VARCHAR、OTHER(默认)

typeAliases

该元素用于为配置文件中的 Java 类型设置一个简短的名字,即设置别名。别名的设置与 XML 配置相关,其使用的意义在于减少全限定类名的冗余。

1
2
3
4
5
6
7
8
9
<!-- 定义别名 -->
<typeAliases>
<!-- alias指定自定义的别名,省略时,默认将类名首字母小写后的名称作为别名 -->
<!-- type指定需要被定义别名的类的全限定名 -->
<typeAlias alias="user" type="com.educode.model.User"/>
<!-- 使用自动扫描包来定义别名 -->
<!-- name指定要被定义别名的包,将包中所有的POJO类以首字母小写的非限定类名来作为它的别名 -->
<package name="com.educode.model"/>
</typeAliases>

可使用注解定义类型别名:

1
2
@Alias(value="user")
public class User{...}

typeHandler

typeHandler 的作用是将预处理语句中传入的参数从 javaType(Java 类型)转换为 jdbcType(JDBC 类型),或者从数据库取出结果时将 jdbcType 转换为 javaType。

MyBatis 框架提供的常用类型处理器

类型处理器 java 类型 JDBC 类型
BooleanTypeHandle java.lang.Boolean, boolean 数据库兼容的 BOOLEAN
ByteTypeHandle java.lang.Byte, byte 数据库兼容的 NUMERIC 或 BYTE
ShortTypeHandle java.lang.Short, short 数据库兼容的 NUMERIC 或 SHORT INTEGER
IntegerTypeHandle java.lang.Integer, int 数据库兼容的 NUMERIC 或 INTEGER
LongTypeHandle java.lang.Long, long 数据库兼容的 NUMERIC 或 LONG INTEGER
FloatTypeHandle java.lang.Float, float 数据库兼容的 NUMERIC 或 FLOAT
DoubleTypeHandle java.lang.Doubel, duoble 数据库兼容的 NUMERIC 或 DOUBLE
StringTypeHandle java.lang.String CHAR 或 VARCHAR
DateTypeHandle java.util.Date TIMESTAMP

可以通过自定义的方式对类型处理器进行扩展,typeHandler 元素用于在配置文件中注册自定义的类型处理器.

1
2
3
4
5
6
<typeHandlers>
<!-- 以单个类的形式配置 -->
<typeHandler handler="com.educode.handler.userTypeHandler"/>
<!-- 注册一个包中所有的typeHandler,系统在启动时会自动扫描包下的所有 -->
<packagename="com.educode.handler"/>
</typeHandlers>

objectFactory

MyBatis 框架每次创建结果对象的新实例时,都会使用一个对象工厂 ObjectFactory 的实例来完成。MyBatis 中默认的 ObjectFactory 的作用就是实例化目标类,它既可以通过默认构造方法实例化,也可以在参数映射存在的时候通过参数构造方法来实例化。

plugins

MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用,这种拦截调用是通过插件来实现的。plugins 元素的作用就是配置用户所开发的插件。

environments

该元素用于多种数据源配置,即配置多种数据库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- default属性指定默认的环境ID -->
<environments default="development">
<environment id="development">
<!-- 使用JDBC事务管理 -->
<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="product">
...
</environment>
</environments>

在 MyBatis 中,可以配置两种类型的事务管理器,分别是 JDBC 和 MANAGED。在 Spring + MyBatis 项目中,一般使用 Spring 自带的管理器来实现事务管理。

MyBatis 框架提供了 UNPOOLED 、POOLED 和 JNDI 三种数据源类型进行数据源配置。实际开发中,一般使用 POOLED 类型,此数据源利用池的概念将 JDBC 连接对象组织起来,避免了在创建新的连接实例时所需要初始化和认证的时间。这种方式使得并发 Web 应用可以快速地响应请求。配置属性说明如下:

属性 说明
poolMaximumActiveConnections 在任意时间可以存在的活动连接数,默认值:10
poolMaximumIdelConnections 任意时间可能存在的空闲连接数
poolMaximumCheckoutTime 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000毫秒
poolTimeToWait 连接等待时间过长,重新尝试获取一个连接,默认值:20000毫秒
poolPingQuery 发送到数据库的侦测查询,用于检验连接是否正常工作
poolPingEnable 是否启用侦测查询,默认值:false
poolPingConnectionsNotUsedFor 配置 poolPingQuery 的使用频度。默认值:0

mappers

该元素用于指定 MyBatis 映射文件的位置,具体指定方法如下所示:

1
2
3
4
5
6
7
8
9
10
<mappers>
<!-- 使用类路径引入 -->
<mapper resource="com/educode/mapper/UserMapper.xml"/>
<!-- 使用本地文件路径引入 -->
<mapper url="file:///D:/com/educode/mapper/UserMapper.xml"/>
<!-- 使用接口类引入 -->
<mapper class="com.educode.mapper.UserMapper"/>
<!-- 使用包名引入 -->
<package name="com.educode.mapper"/>
</mappers>

任务代码

mybatisConfig.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?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>
<!-- ********* Begin ********* -->
<!-- 1. 引入 db.properties 配置文件 -->
<properties resource="db.properties"/>
<!-- 2. 使用自动扫描 com.educode.model 包来定义别名 -->
<typeAliases>
<package name="com.educode.model"/>
</typeAliases>
<!-- 3.配置环境,默认环境 id 为 mysql -->
<environments default="mysql">
<environment id="mysql">
<!-- 3.1 使用JDBC事务管理 -->
<transactionManager type="JDBC"/>
<!-- 3.2 配置数据源 -->
<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>
<!-- 4.配置 Mapper 的位置, 路径为: mapper/CustomerMapper.xml -->
<mappers>
<!-- 使用类路径引入 -->
<mapper resource="mapper/CustomerMapper.xml"/>
</mappers>
<!-- ********* End ********* -->
</configuration>

3.MyBatis的映射器

MyBatis 框架的强大之处就体现在映射文件的编写上。mapper 元素是映射文件的根元素,其他元素都是它的子元素。这些子元素及其作用如图2所示:
图2-映射文件主要元素
select

该元素用于映射查询语句,示例如下:

1
2
3
<select id="findCustomerById" parameterType="Integer" resultType="com.educode.model.Customer">
select * from t_customer where id=#{id}
</select>

该查询语句的唯一标识为 findCustomerById,它接收一个 Integer 类型的参数,并返回一个 Customer 类型的对象。

select常用可配置属性

属性 说明
id 表示命名空间中的唯一标识,常与命名空间组合起来使用
parameterType 传入 SQL 语句的参数类的全限定名或者别名
resultType 从 SQL 语句中返回的类型的类的全限定名或者别名
resultMap 表示外部 resultMap 的命名引用
flushCache 执行 SQL 语句后,是否需要清空之前查询的本地缓存和二级缓存,默认值:false
userCache 是否开启二级缓存,默认值:false
timeout 设置超时参数,单位为秒。超时将抛出异常
fetchSize 获取记录的总条数设定,默认值:unset(依赖于驱动)
statementType 设置 MyBatis 工作 JDBC 的 Statement
resultSet 表示结果集的类型,默认值:unset(依赖于驱动)

insert

该元素用于映射插入语句,在执行完元素中定义的 SQL 语句后,会返回插入的记录数。该元素的属性与 select 元素的属性大部分相同,但还包含了3个特有属性.

属性 说明
keyProperty 将插入或更新操作时的返回值赋给指定返回类的某个属性,常设置为主键对应的属性
keyColumn 设置第几列是主键,当主键不是列表中第一列时需要设置
useGeneratedKeys 获取数据库内部生成的主键,如 MySQL 的自动递增字段,默认值:false

当执行插入操作后需要返回插入成功的数据生成的主键值时,可通过以上3个属性实现,示例如下:

1
2
3
<insert id="addCustomer" parameterType="com.educode.model.Customer" keyProperty="id" useGeneratedKeys="true">
insert into t_customer(name,jobs)values(#{username},#{jobs})
</insert>

若使用数据库不支持主键自动增长或消除了主键自增的规则时,MyBatis 可自定义生成主键,示例如下:

1
2
3
4
5
6
7
<insert id="insertCustomer" parameterType="com.educode.model.Customer">
<selectKey keyProperty="id" resultType="Integer" order="BEFORE">
<!-- 如果t_customer表中没有记录,则将id设置为1,否则就将id的最大值加1,来作为新的主键 -->
select if(max(id) is null,1,max(id)+1) as newId from t_customer
</selectKey>
insert into t_customer(id,name,jobs)values(#{id},#{name},#{jobs})
</insert>

其中 order 属性如果设置为 BEFORE,那么它会首先执行 selectKey 元素中的配置来设置主键,然后执行插入语句;如果设置为 AFTER,那么它会先执行插入语句,然后执行 selectKey 元素中的配置内容。

update、delete

update 和 delete 元素的属性基本与 select 元素中的属性一致。在执行完元素中定义的 SQL 语句后,会返回影响的记录数。示例如下:

1
2
3
4
5
6
7
8
<!-- 更新信息 -->
<update id="updateCustomer" parameterType="com.educode.model.Customer">
update t_customer set name=#{name},jobs=#{jobs} where id=#{id}
</update>
<!-- 删除信息 -->
<delete id="deleteCustomer" parameterType="Integer">
delete from t_customer where id=#{id}
</delete>

sql

该元素的作用就是定义可重用的 SQL 代码片段,然后在其他语句中引用这一代码片段。示例如下:

1
2
3
4
5
6
<!-- 定义一个包含id、username、jobs和phone字段的代码片段 -->
<sql id="customerColumns">id,username,jobs,phone</sql>
<select id="findCustomerById" parameterType="Integer" resultType="com.educode.Customer">
<!-- refid属性引用了自定义的代码片段 -->
select <include refid="customerColumns"/> from t_customer where id=#{id}
</select>

resultMap

该元素表示结果映射集。主要作用是定义映射规则、级联的更新以及定义类型转化器等。其包含的子元素及结构说明如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- resultMap的元素结构 -->
<resultMap type="" id="">
<!-- 类在实例化时,用来注入结果到构造方法中 -->
<constructor>
<!-- ID参数;标记结果作为ID -->
<idArg/>
<!-- 注入到构造方法的一个普通结果 -->
<arg/>   
</constructor>
<!-- 用于表示哪个列是主键 -->
<id/>     
<!-- 注入到字段或JavaBean属性的普通结果 -->
<result/>   
<!-- 用于一对一关联 -->
<association property=""/> 
<!-- 用于一对多关联 -->
<collection property=""/> 
<!-- 使用结果值来决定使用哪个结果映射 -->
<discriminator javaType="">
<!-- 基于某些值的结果映射 -->
<casevalue=""/>    
</discriminator>
</resultMap>

在默认情况下,MyBatis 程序在运行时会自动地将查询到的数据与需要返回的对象的属性进行匹配赋值(需要表中的列名与对象的属性名称完全一致)。然而实际开发时,数据表中的列和需要返回的对象的属性可能不会完全一致,这种情况下 MyBatis 是不会自动赋值的。此时,就可以使用 resultMap 元素进行处理。

任务代码

CustomerMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?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="mapper.CustomerMapper.xml">
<!-- 新增客户 -->
<insert id="addCustomer" parameterType="customer">
insert into customer(name,jobs,phone) value(#{name},#{jobs},#{phone})
</insert>

<!-- 根据客户姓名查找客户信息 -->
<select id="findCustomerByName" resultType="customer"
parameterType="java.lang.String">
select * from customer where name = #{name}
</select>
<!-- ********* Begin ********* -->
<!-- 定义一个包含id、username字段的代码片段 -->
<sql id="customerColumns">id,name</sql>

<!-- 新增客户,需返回插入数据生成的主键值id, id=addCustomerKey -->
<insert id="addCustomerKey" parameterType="customer" keyProperty="id" useGeneratedKeys="true">
insert into customer(name,jobs)values(#{name},#{jobs})
</insert>

<!-- 根据id查找客户信息,使用SQL代码片段设置返回属性, id=findCustomerById -->
<select id="findCustomerById" parameterType="Integer" resultType="customer">
<!-- refid属性引用了自定义的代码片段 -->
select <include refid="customerColumns"/> from customer where id=#{id}
</select>

<!-- 根据id更新客户name、jobs和phone信息, id=updateCustomer -->
<update id="updateCustomer" parameterType="customer">
update customer set name=#{name},phone=#{phone},jobs=#{jobs} where id=#{id}
</update>

<!-- 根据id删除客户信息, id=deleteCustomer -->
<delete id="deleteCustomer" parameterType="Integer">
delete from customer where id=#{id}
</delete>

<!-- ********* End ********* -->
</mapper>

4.动态SQL

开发人员在使用 JDBC 或其他类似的框架进行数据库开发时,通常都要根据需求去手动拼装 SQL,这样非常繁琐。而 MyBatis 提供的对 SQL 语句动态组装的功能很方便的完成 SQL 拼装。

MyBatis 采用了功能强大的基于 OGNL 的表达式来完成动态 SQL。其主要元素说明如下:

元素 说明
if 判断语句,用于单条件分支判断
choose(when、otherwise) 用于多条件分支判断,相当于的 java 中 switch
where、trim、set 辅助元素、用于处理一些 SQL 拼装、特殊字符问题
foreach 循环语句,常用语in语句等列举条件中
bind 创建一个变量,并绑定到上下文中,常用于模糊查询

if

该元素是最常用的判断语句,它类似于 Java 中的 if 语句,主要用于实现某些简单的条件选择。示例如下:

1
2
3
4
5
6
7
8
9
<select id="findCustomer" parameterType="com.educode.Customre" resultType="com.educode.Customer">
select * form t_customer where 1=1
<if test="name != null">
and name like = #{name}
</if>
<if test="jobs != null">
and jobs like = #{jobs}
</if>
</select>

使用 if 元素的 test 属性分别对 name 和 jobs 进行了非空判断(test 属性多用于条件判断语句中,用于判断真假,大部分的场景中都是进行非空判断,有时候也需要判断字符串、数字和枚举等),如果传入的查询条件非空就进行动态 SQL 组装。

choose、when、otherwise

这些元素用于多条件分支判断,相当于的 java 中 switch 语句。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<select id="findCustomerWhen" resultType="customer" parameterType="customer">
select * from customer where 1 = 1
<choose>
<when test="name != null">
and name=#{name}
</when>
<when test="jobs != null">
and jobs=#{jobs}
</when>
<otherwise>
and phone is not null
</otherwise>
</choose>
</select>

上述代码中,使用了 choose 元素进行 SQL 拼接,当第一个 when 元素中的条件为真,则只动态组装第一个 when 元素内的 SQL 片段,否则就继续向下判断第二个 when 元素中的条件是否为真,以此类推。当前面所有 when 元素中的条件都不为真时,则只组装 otherwise 元素内的 SQL 片段。

where、trim

where、trim 元素用于处理一些 SQL 拼装。如使用 where、trim 元素替换映射文件中的where 1=1条件,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<select id="findCustomerWhere" resultType="customer" parameterType="customer">
select * from customer
<where>
<if test="name != null">
and name=#{name}
</if>
<if test="jobs != null">
and jobs=#{jobs}
</if>
</where>
</select>
<select id="findCustomerTrim" resultType="customer" parameterType="customer">
select * from customer
<trim prefix="where" prefixOverrides="and">
<if test="name != null">
and name=#{name}
</if>
<if test="jobs != null">
and jobs=#{jobs}
</if>
</trim>
</select>

where元素会自动判断组合条件下拼装的 SQL 语句,只有 where 元素内的条件成立时,才会在拼接 SQL 中加入 where 关键字,否则将不会添加;即使 where 之后的内容有多余的AND或OR, where 元素也会自动将它们去除。

trim元素的作用是去除一些特殊的字符串,它的 prefix 属性代表的是语句的前缀(这里使用 where 来连接后面的 SQL 片段),而 prefixOverrides 属性代表的是需要去除的那些特殊字符串(这里定义了要去除 SQL 中的 and)。

set

该元素主要用于更新操作,其作用是在动态包含的 SQL 语句前输出一个 SET 关键字,并将 SQL 语句中最后一个多余的逗号去除。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<update id="updateCustomerSet" parameterType="customer">
update customer
<set>
<if test="name != null">
name=#{name},
</if>
<if test="jobs != null">
jobs=#{jobs},
</if>
<if test="phone != null">
phone=#{phone},
</if>
</set>
where id=#{id}
</update>

上述代码中,使用了 set 和 if 元素相结合的方式来组装 update 语句。其中 set 元素会动态前置 SET 关键字,同时也会消除 SQL 语句中最后一个多余的逗号;if 元素用于判断相应的字段是否传入值,如果传入的更新字段非空,就将此字段进行动态 SQL 组装,并更新此字段,否则此字段不执行更新。

foreach

该元素用于数组和集合循环的遍历,通常在构建 IN 条件语句时使用,其使用方式如下:

1
2
3
4
5
6
7
<select id="findCustomerByIds" resultType="customer" parameterType="List">
select * from customer where id in
<foreach item="id" index="index" collection="list"
open="(" separator="," close=")">
#{id}
</foreach>
</select>

上述代码中,使用了 foreach 元素对传入的集合进行遍历并进行了动态 SQL 组装,其中使用属性说明如下:

属性 说明
item 配置的是当前循环的元素
index 配置的是当前元素在集合的位置下班
collection 配置的list是传递过来的参数类型(如array、list、Map集合的键)
open、close 配置的是以什么符号将这些集合元素包装起来。
separator 配置的是各个元素的间隔符

bind

该元素可以通过 OGNL 表达式来创建一个上下文变量。示例如下:

1
2
3
4
5
<select id="findCustomerByPatternName" resultType="customer"
parameterType="customer">
<bind name="pattern_name" value="'%'+ name +'%'"/>
select * from customer where name like #{pattern_name}
</select>

上述代码中,使用 bind 元素定义了一个 name 为 pattern_username 的变量, bind 元素中 value 的属性值就是拼接的查询字符串,其中_parameter.getUsername()表示传递进来的参数(也可以直接写成对应的参数变量名,如 username)。在 SQL 语句中,直接引用 bind 元素的 name 属性值即可进行动态 SQL 组装。

任务代码

CustomerMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<?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="mapper.CustomerMapper.xml">
<!-- 新增客户 -->
<insert id="addCustomer" parameterType="Customer">
insert into customer(name,jobs,phone) value(#{name},#{jobs},#{phone})
</insert>
<!-- 根据客户姓名查找客户信息 -->
<select id="findCustomerByName" resultType="Customer"
parameterType="java.lang.String">
select * from customer where name = #{name}
</select>

<!-- ********* Begin ********* -->
<!-- 使用 if 元素编写根据客户姓名和职业组合条件查询客户信息的动态SQL, id=findCustomerIf -->
<select id="findCustomerIf" resultType="Customer" parameterType="Customer" >
select * from customer where 1 = 1
<if test="name != null">
and name=#{name}
</if>
<if test="jobs != null">
and jobs=#{jobs}
</if>

</select>
<!-- 使用 where 元素编写编写根据客户姓名和职业组合条件查询客户信息的动态SQL, id=findCustomerWhere -->
<select id="findCustomerWhere" resultType="Customer" parameterType="Customer">
select * from customer
<where>
<if test="name != null">
and name=#{name}
</if>
<if test="jobs != null">
and jobs=#{jobs}
</if>
</where>
</select>
<!-- 使用 trim 元素编写编写根据客户姓名和职业组合条件查询客户信息的动态SQL, id=findCustomerTrim -->
<select id="findCustomerTrim" resultType="Customer" parameterType="Customer">
select * from customer
<trim prefix="where" prefixOverrides="and">
<if test="name != null">
and name=#{name}
</if>
<if test="jobs != null">
and jobs=#{jobs}
</if>
</trim>
</select>
<!-- 使用 foreach 元素编写根据用户id列表查询客户信息的动态SQL, id=findCustomerByIds -->
<select id="findCustomerByIds" resultType="Customer" parameterType="List">
select * from customer where id in
<foreach item="id" index="index" collection="list"
open="(" separator="," close=")">
#{id}
</foreach>
</select>
<!-- 使用 bind 元素编写根据客户名模糊查询客户信息的动态SQL, id=findCustomerByPatternName -->
<select id="findCustomerByPatternName" resultType="Customer"
parameterType="Customer">
<bind name="pattern_name" value="'%'+ name +'%'"/>
select * from customer where name like #{pattern_name}
</select>
<!-- ********* End ********* -->
</mapper>

5.Mybatis的关联映射

针对多表之间的操作,MyBatis 提供了关联映射,可以很好地处理对象与对象之间的关联关系。

一对一

一对一的关系就是在本类中定义对方类型的对象,如 A 类中定义 B 类类型的属性 b,B 类中定义 A 类类型的属性 a。MyBatis 通过 resultMap 元素的子元素 association 元素来处理一对一关联关系,通常可以配置以下属性:

属性 说明
property 指定映射到的实体类对象属性,与表字段一一对应
column 指定表中对应的字段
javaType 指定映射到实体对象属性的类型
select 指定引入嵌套查询的子 SQL 语句,该属性用于关联映射中的嵌套查询
fetchType 指定在关联查询时是否启用延迟加载,属性值:lazy(默认)、eager

association 元素的使用可参考如下两种示例配置:

1
2
3
4
5
6
7
8
9
<!-- 嵌套查询 -->
<association property="idCard" javaType="IdCard"
select="com.educode.mapperIdCardMapper.findCodeById">
</association>
<!-- 嵌套结果 -->
<association property="idCard" javaType="IdCard">
<id property="id" column="card_id" />
<result property="code" column="code" />
</association>

MyBatis 嵌套查询的方式要执行多条 SQL 语句,这对于大型数据集合和列表展示不是很好,因为这样可能会导致成百上千条关联的 SQL 语句被执行,从而极大地消耗数据库性能并且会降低查询效率。所以通常使用 MyBatis 提供的嵌套结果方式,来进行关联查询。

以个人和身份证之间的一对一关联关系为例,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<select id="findPersonCard" resultMap="IdCardWithPersonResult"
parameterType="java.lang.Integer">
select p.*,card.code
from person p,idcard card
where p.card_id=card.id
and p.id=#{id}
</select>
<resultMap id="IdCardWithPersonResult" type="Person">
<id property="id" column="id" />
<result property="name" column="name"/>
<association property="idCard" javaType="IdCard">
<id property="id" column="card_id" />
<result property="code" column="code" />
</association>
</resultMap>

一对多

一对多的关系就是一个 A 类类型对应多个 B 类类型的情况,需要在 A 类中以集合的方式引入 B 类类型的对象,在 B 类中定义 A 类类型的属性 a。

MyBatis 通过 resultMap 元素的子元素 collection 元素来处理一对多关联关系。其属性大部分与 association 元素相同,但其还包含一个特殊属性 ofType,ofType 属性与 javaType 属性对应,用于指定实体对象中集合类属性所包含的元素类型。

collection 元素的使用可参考如下两种示例配置:

1
2
3
4
5
6
7
8
9
<!-- 嵌套查询 -->
<collection property="ordersList" column="id" ofType="Orders"
selett="com.educode.mapper.OrdersMapper.selectOrders">
</collection>
<!-- 嵌套结果 -->
<collection property="ordersList" ofType="Orders">
<id property="id" column="orders_id" />
<result property="number" column="number" />
</collection>

以一个用户可以有多个订单为例,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<select id="findPersonOrders" resultMap="OrdersWithPersonResult"
parameterType="java.lang.Integer">
select p.*,o.id as orders_id,o.number,o.person_id
from person p,orders o
where p.id=o.person_id
and p.id=#{id}
</select>
<resultMap id="OrdersWithPersonResult" type="Person">
<id property="id" column="id" />
<result property="name" column="name"/>
<collection property="ordersList" ofType="Orders">
<id property="id" column="orders_id" />
<result property="personId" column="person_id" />
<result property="number" column="number" />
</collection>
</resultMap>

多对多

多对多的关系就是在A类中定义B类类型的集合,在 B 类中定义 A 类类型的集合。在实际项目开发中,多对多的关联关系也是非常常见的。

以订单和商品为例,一个订单可以包含多种商品,而一种商品又可以属于多个订单,订单和商品就属于多对多的关联关系。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 多对多嵌套结果查询:查询某订单及其关联的商品详情 -->
<select id="findOrders" parameterType="java.lang.Integer"
resultMap="OrdersWithProductResult">
select * from orders where id = #{id}
</select>
<resultMap id="OrdersWithProductResult" type="Orders">
<id property="id" column="id"/>
<result property="number" column="number"/>
<collection property="productList" column="id" ofType="Product"
select="com.educode.mapper.ProductMapper.findProductById">
</collection>
</resultMap>
<select id="findProductById" parameterType="java.lang.Integer"
resultType="Product">
select * from product where
id=(selete product_id from ordersitem where orders_id=#{id})
</select>

上述代码中,使用嵌套查询的方式定义了一个 id 为 findOrdersWithPorduct 的 select 语句来查询订单及其关联的商品信息。在 resultMap 元素中使用了 collection 元素来映射多对多的关联关系,其中 property 属性表示订单持久化类中的商品属性,ofType 属性表示集合中的数据为 Product 类型,而 column 的属性值会作为参数执行 ProductMapper 中定义的 id 为 findProductById 的执行语句来查询订单中的商品信息。

定义了一个 id 为 findProductById 的执行语句,该执行语句中的 SQL 会根据订单 id 查询与该订单所关联的商品信息。由于订单和商品是多对多的关联关系,所以需要通过中间表 ordersitem 来查询商品信息。

任务代码

PersonMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?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="mapper.PersonMapper.xml">
<!-- ********* Begin ********* -->
<!-- 根据 id 查找个人信息与身份证号 -->
<select id="findPersonCard" resultMap="IdCardWithPerson"
parameterType="java.lang.Integer">
select p.*,card.code
from person p,idcard card
where p.card_id=card.id
and p.id=#{id}
</select>
<resultMap id="IdCardWithPerson" type="Person">
<id property="id" column="id" />
<result property="name" column="name"/>
<association property="idCard" javaType="IdCard">
<id property="id" column="card_id" />
<result property="code" column="code" />
</association>
</resultMap>
<!-- 根据 id 查找个人信息与订单信息 -->
<select id="findPersonOrders" resultMap="OrdersWithPersonResult"
parameterType="java.lang.Integer">
select p.*,o.id as orders_id,o.number,o.person_id
from person p,orders o
where p.id=o.person_id
and p.id=#{id}
</select>
<resultMap id="OrdersWithPersonResult" type="Person">
<id property="id" column="id" />
<result property="name" column="name"/>
<collection property="ordersList" ofType="Orders">
<id property="id" column="orders_id" />
<result property="personId" column="person_id" />
<result property="number" column="number" />
</collection>
</resultMap>
<!-- ********* End ********* -->
</mapper>

6.MyBatis的插件开发

Mybatis 插件也叫 Mybatis 拦截器,实际上它就是一个拦截器,通过应用代理模式,在方法级别上进行拦截。

它支持拦截以下方法:

  • 执行器 Executor(update、query、commit、rollback 等方法);
  • 参数处理器 ParameterHandler(getParameterObject、setParameters方法);
  • 结果集处理器 ResultSetHandler(handleResultSets、handleOutputParameters 等方法);
    SQL
  • 语法构建器 StatementHandler(prepare、parameterize、batch、update、query 等方法)。

拦截器接口介绍
MyBatis 插件需要实现拦截器接口 Interceptor (org.apache.ibatis.plugin Interceptor ),在实现类中对拦截对象和方法进行处理,该接口定义如下:

1
2
3
4
5
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}

从上述代码可以看出,该接口有三个方法:

  • intercept 方法:该方法是 MyBatis 运行时要执行的拦截的方法,通过该方法的参数 invocation 可以得到很多有用的信息,该参数的 getTarget() 方法可以获取当前被拦截的对象,getMethod() 方法可以获取当前被拦截的方法,getArgs() 方法可以获取被拦截方法中的参数。通过调用 invocation.proceed() 方法可以执行被拦截对象真正的方法;
  • plugin 方法:target 是被拦截的对象,它的作用是给被拦截对象生成一个代理对象,只需要调用 Mybatis 提供的 Plugin 类的 wrap 静态方法就可以通过 Java 的动态代理拦截目标对象;
  • setProperties 方法:允许在 plugin 元素中配置所需参数,该方法在插件初始化的时候会被调用一次。

拦截器签名

除了需要实现拦截器接口外,还需要给实现类配置以下拦截器注解。
比如想拦截 StatementHandler 对象的 prepare 方法,该方法有一个参数Connection 对象,可以这样声明:

1
2
3
4
5
6
7
8
@Intercepts({
@Signature(
type =StatementHandler.class,
method="prepare" ,
args={Connection.class})})
public class MyPlugin implements Interceptor{
......
}

上述代码中,@Signature 注解包含以下三个属性:

  • type:设置拦截的接口;
  • method:设置拦截接口中的方法名;
  • args:设置拦截方法的参数类型数组,通过方法名和参数类型可以确定唯一一个方法。

自定义插件示例

编写类 MyInterceptor 实现拦截器接口 Interceptor,设置拦截器签名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.yy;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import java.util.Properties;
//完成插件签名,用于拦截StatementHandler对象的parameterize方法
@Intercepts(@Signature(type = StatementHandler.class,method = "parameterize",args=java.sql.Statement.class))
public class MyInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget();
// 获取MetaObject对象
MetaObject metaObject = SystemMetaObject.forObject(target);
// 获取参数值
Object value = metaObject.getValue("parameterHandler.parameterObject");
// 设置参数值
metaObject.setValue("parameterHandler.parameterObject", 3);
//执行目标方法
Object proceed = invocation.proceed();
//返回执行后的返回值
return proceed;
}
@Override
public Object plugin(Object o) {
Object wrap = Plugin.wrap(o, this);
//返回为当前target创建的动态代理
return wrap;
}
@Override
public void setProperties(Properties properties) {
}
}

上述代码中,插件签名用于拦截 StatementHandler 对象的 parameterize方法,该插件拦截器可以实现将参数值改为 3。

在 mybatis 的全局配置文件配置我们自定义的插件:

1
2
3
4
<plugins>
<plugin interceptor="com.yy.MyInterceptor">
</plugin>
</plugins>

配置完自定义插件后,当我们执行我们的 mybatis 代码时,会自动加载该插件,执行拦截任务。

任务代码

MyInterceptor.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.yy;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import java.util.Properties;

// 完成插件签名,用于拦截StatementHandler对象的parameterize方法
@Intercepts(@Signature(type = StatementHandler.class,method = "parameterize",args=java.sql.Statement.class))
public class MyInterceptor implements Interceptor {

@Override
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget();
MetaObject metaObject = SystemMetaObject.forObject(target);
// 获取参数值
Object value = metaObject.getValue("parameterHandler.parameterObject");
/********* 请在此编写代码 **********/
/********* Begin **********/
int id=Integer.parseInt(String.valueOf(value));
metaObject.setValue("parameterHandler.parameterObject", id+1);
/********* End **********/
// 执行目标方法
Object proceed = invocation.proceed();
// 返回执行后的返回值
return proceed;
}

@Override
public Object plugin(Object o) {
Object wrap = Plugin.wrap(o, this);
// 返回为当前target创建的动态代理
return wrap;
}

@Override
public void setProperties(Properties properties) {

}
}

mybatis_config.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?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>
<!-- 配置参件 -->
<!-- Begin -->
<plugins>
<plugin interceptor="com.yy.MyInterceptor">
</plugin>
</plugins>
<!-- End -->
<!-- 和spring整合后 environments配置将废除 -->
<environments default="development">
<environment id="development">
<!-- 使用JDBC事务管理 -->
<transactionManager type="JDBC"/>
<!-- 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb?useUnicode=true&amp;characterEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="123123"/>
</dataSource>
</environment>
</environments>
<!-- 加载映射文件 -->
<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>
<!-- mybatis-config.xml 注册插件-->
</configuration>

MyBatis的解析和运行原理

1.构建SqlSessionFactory过程

MyBatis运行过程
图3-MyBatis 的运行过程
上图中各个对象的解释如下:
图4-解释

SqlSessionFactory 创建

SqlSessionFactory 是 MyBatis 的核心类之一,它最重要的功能就是提供 MyBatis 的核心接口 SqlSession。MyBatis 使用了 Builder 模式去创建 SqlSessionFactory,在开发中我们可以通过 SqlSessionFactoryBuilder 去构建,如下所示:

1
2
3
4
// 读取MyBatis核心配置文件
Reader reader =Resources.getResourceAsReader("mybatis_config.xml");
// 通过reader实例化sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

上述构建步骤分为两步,第一步使通过 org.apache.ibatis builder.xml.XMLConfigBuilder 解析配置的 XML 文件,读出所配置的参数,并将读取的内容存入 org.apache.ibatis.session.Configuration 类对象中。

第二步使用 Confinguration 对象去创建 SqlSessionFactory。SqlSessionFactory 是一个接口,而不是一个实现类,为此 MyBatis 提供了一个默认的实现类org.apache.ibatis.session.defaults.DefaultSqlSessionFactory。在大部分情况下都没有必要自己去创建新 SqlSessionFactory 实现类。

Configuration的作用

SqlSessionFactory 构建中,Configuration 是最重要的,它的作用是:

  • 读入配置文件,包括基础配置的 XML 和映射器 XML(或注解〉。
  • 初始化一些基础配置,比如 MyBatis 的别名等,一些重要的类对象(比如插件、映射器、Object 工厂、typeHandlers 对象等)。
  • 提供单例,为后续创建 SessionFactory 服务,提供配置的参数。
  • 执行一些重要对象的初始化方法。

任务

图5-构建 SqlSessionFactory

2.SqlSession运行过程

获取SqlSession对象

获取 SqlSession 对象我们用的是以下代码:

1
sqlSession = sqlSessionFactory.openSession();

上述代码执行后,SqlSessionFactory 的 openSession 方法会获取 SqlSession 接口的实现类 DefaultSqlSession 对象。

有了 DefaultSqlSession 对象,我们以查询一条数据为例,讲解一下整个处理过程:

1
2
3
4
5
6
7
SqlSession session = sqlSessionFactory.openSession();
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
} finally {
session.close();
}

上述代码中,我们首先获取了 SqlSession 对象,然后通过该对象调用 Mapper 接口中的 selectBlog 方法实现对数据库的操作。

通过 DefaultSqlSession 对象的 getMapper 方法获取的是一个 MapperProxy 代理对象,这也是 Mapper 接口不用实现类的原因。当调用 BlogMapper 中的方法时,由于 BlogMapper 是一个 JDK 动态代理对象,它会运行 invoke 方法。

invoke 方法会判断代理的对象是否是一个类,由于代理对象是一个接口,所以通过 cachedMapperMethod 生成一个 MappedMethod 对象,然后执行 execute 方法。

SqlSession 下的四大对象

映射器就是一个动态代理对进入到了 MapperMethod 的 execute 方法,然后它经过简单地判断就进入了 SqlSession 的 delete、update、insert、select 等方法,那么这些方法是如何执行呢?
实际上 SqlSession 是通过 Executor、StatementHandle、ParamterHandler、ResultSetHandler 来完成数据库操作和结果返回的。

  • Executor 代表执行器,由它调度 StatementHandler、ParameterHandler、 ResultSetHandler 等来执行对应的 SQL 。其中 StatementHandler 是最重要;

  • StatementHandler 的作用是使用数据库 Statement ( PreparedStatement) 执行操作,它是四大对象的核心,起到承上启下的作用,许多重要的插件都是通过拦截它来实现的;

  • ParameterHandle 是用来处理 SQL 参数的;

  • ResultSetHandl 是进行数据集 (ResultSet) 封装返回处理的。

执行过程如下:

SqlSession 是通过执行器 Executor 调度 StatementHandler 来运行的,而 StatementHandler 经过以下 3 步:

  • prepared 预编译 SQL;
  • parameterize 设置参数;
  • query/update 执行 SQL。

其中,parameterize 是调用 parameterHandler 的方法设置的,而参数是根据类型处理器 typeHandler 处理的。query/update 方法通过 ResultSetHandler 进行处理结果的封装,如果是 update 语旬,就返回整数,否则就通过 typeHandler 处理结果类型,然后用 ObjectFactory 提供的规则组装对象,返回给调用者。

任务

图6-SqlSession 运行过程

MyBatis: 1-5

MyBatis的插件开发:6

MyBatis 的解析和运行原理:1-2


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!