# MyBatis 简介

MyBatis 的前身是 Apache 的开源项目 iBatis。MyBatis 几乎可以代替 JDBC,是一个支持定制化 SQL 查询,存储过程和高级映射的基于 Java 的优秀持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以对配置和原生 Map 使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java 对象)映射成数据库中的记录。

安装 MyBatis

下载 jar 包并置于 classpath 下,或使用 maven 导入:

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
</dependency>

Mybatis 的功能架构

  1. API 接口层:提供给外部使用的接口 API,开发人员通过这些本地 API 来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
  2. 数据处理层:负责具体的 SQL 查找、SQL 解析、SQL 执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
  3. 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。

MyBatis 的优缺点

优点:

  • 灵活:mybatis 不会对应用程序或者数据库的现有设计强加任何影响。 sql 写在 xml 里,便于统一管理和优化。通过 sql 基本上可以实现我们不使用数据访问框架可以实现的所有功能,或许更多。
  • 解除 sql 与程序代码的耦合:通过提供 DAL 层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql 和代码的分离,提高了可维护性。
  • 提供映射标签,支持对象与数据库的 orm 字段关系映射。
  • 提供对象关系映射标签,支持对象关系组建维护。
  • 提供 xml 标签,支持编写动态 sql。

缺点:

  • 编写 SQL 语句时工作量很大,尤其是字段多、关联表多时,更是如此。
  • SQL 语句依赖于数据库,导致数据库移植性差,不能更换数据库。
  • 框架还是比较简陋,功能尚有缺失,虽然简化了数据绑定代码,但是整个底层数据库查询实际还是要自己写的,工作量也比较大,而且不太容易适应快速数据库修改。
  • 二级缓存机制不佳。

# Hibernate 和 MyBatis 对比

  1. SQL 优化方面
    • Hibernate 不需要编写大量的 SQL,就可以完全映射,提供了日志、缓存、级联(级联比 MyBatis 强大)等特性,此外还提供 HQL(Hibernate Query Language)对 POJO 进行操作。但会多消耗性能。
    • MyBatis 手动编写 SQL,支持动态 SQL、处理列表、动态生成表名、支持存储过程。工作量相对大些。
  2. 开发方面
    • MyBatis 是一个半自动映射的框架,因为 MyBatis 需要手动匹配 POJO、SQL 和映射关系。
    • Hibernate 是一个全表映射的框架,只需提供 POJO 和映射关系即可。
  3. Hibernate 的优势
    • Hibernate 的 DAO 层开发比 MyBatis 简单,Mybatis 需要维护 SQL 和结果映射。
    • Hibernate 对对象的维护和缓存要比 MyBatis 好,对增删改查的对象的维护要方便。
    • Hibernate 数据库移植性很好,MyBatis 的数据库移植性不好,不同的数据库需要写不同 SQL。
    • Hibernate 有更好的二级缓存机制,可以使用第三方缓存。MyBatis 本身提供的缓存机制不佳。
  4. Mybatis 的优势
    • MyBatis 可以进行更为细致的 SQL 优化,可以减少查询字段。
    • MyBatis 容易掌握,而 Hibernate 门槛较高。

总的来说,MyBatis 是一个小巧、方便、高效、简单、直接、半自动化的持久层框架,Hibernate 是一个强大、方便、高效、复杂、间接、全自动化的持久层框架。所以对于性能要求不太苛刻的系统,比如管理系统、ERP 等推荐使用 Hibernate,而对于性能要求高、响应快、灵活的系统则推荐使用 MyBatis。

# MyBatis 工作原理

mybatis工作原理

  1. 读取 MyBatis 配置文件: mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。
  2. 加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。
  3. 构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。
  4. 创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。
  5. Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。
  6. MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。
  7. 输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。
  8. 输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。

# MyBatis 核心组件

MyBatis 的核心组件分为 4 个部分:

  1. SqlSessionFactoryBuilder(构造器):它会根据配置或者代码来生成 SqlSessionFactory,采用的是分步构建的 Builder 模式。
  2. SqlSessionFactory(工厂接口):依靠它来生成 SqlSession,使用的是工厂模式。
  3. SqlSession(会话):一个既可以发送 SQL 执行返回结果,也可以获取 Mapper 的接口。在现有的技术中,一般我们会让其在业务逻辑代码中 “消失”,而使用的是 MyBatis 提供的 SQL Mapper 接口编程技术,它能提高代码的可读性和可维护性。
  4. SQL Mapper(映射器):MyBatis 新设计存在的组件,它由一个 Java 接口和 XML 文件(或注解)构成,需要给出对应的 SQL 和映射规则。它负责发送 SQL 去执行,并返回结果。

# SqlSessionFactory

MyBatis 提供构造器 SqlSessionFactoryBuilder ,采用的是 Builder 模式生产 SqlSessionFactory 。

在 MyBatis 中,既可以通过读取配置的 XML 文件的形式生成 SqlSessionFactory,也可以通过 Java 代码的形式去生成 SqlSessionFactory。

SqlSessionFactory 是一个接口,在 MyBatis 中它存在两个实现类:SqlSessionManager 和 DefaultSqlSessionFactory。一般而言,具体是由 DefaultSqlSessionFactory 去实现的,而 SqlSessionManager 使用在多线程的环境中,它的具体实现依靠 DefaultSqlSessionFactory 。

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的,而 SqlSessionFactory 唯一的作用就是生产 MyBatis 的核心接口对象 SqlSession,所以它的责任是唯一的。

# 使用 XML 构建 SqlSessionFactory(推荐)

配置示例(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>
    <typeAliases><!-- 别名 -->
        <typeAliases alias="user" type="com.mybatis.po.User"/>
    </typeAliases>
    <!-- 数据库环境 -->
    <environments default="development">
        <environment id="development">
            <!-- 使用 JDBC 的事务管理 -->
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <!-- MySQL 数据库驱动 -->
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <!-- 连接数据库的 URL -->
                <property name="url"
                    value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8" />
                <property name="username" value="root" />
                <property name="password" value="1128" />
            </dataSource>
        </environment>
    </environments>
    <!-- 将 mapper 文件加入到配置文件中 -->
    <mappers>
        <mapper resource="com/mybatis/mapper/UserMapper.xml" />
    </mappers>
</configuration>

生成 SqlSessionFactory.class

SqlSessionFactory factory = null;
String resource = "mybatis-config.xml";
InputStream is;
try {
    InputStream is = Resources.getResourceAsStream(resource);
    factory = new SqlSessionFactoryBuilder().build(is);
} catch (IOException e) {
    e.printStackTrace();
}

# 使用代码创建 SqlSessionFactory(不推荐)

// 数据库连接池信息
PooledDataSource dataSource = new PooledDataSource();
dataSource.setDriver("com.mysql.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword ("1128");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis");
dataSource.setDefeultAutoCommit(false);
// 采用 MyBatis 的 JDBC 事务方式
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment ("development", transactionFactory, dataSource);
// 创建 Configuration 对象
Configuration configuration = new Configuration(environment);
// 注册一个 MyBatis 上下文别名
configuration.getTypeAliasRegistry().registerAlias("role", Role.class);
// 加入一个映射器
configuration.addMapper(RoleMapper.class);
// 使用 SqlSessionFactoryBuilder 构建 SqlSessionFactory
SqlSessionFactory SqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
return SqlSessionFactory;

# SqlSession

在 MyBatis 中, SqlSession 是其核心接口。在 MyBatis 中有两个实现类,DefaultSqlSession 和 SqlSessionManager。DefaultSqlSession 是单线程使用的,而 SqlSessionManager 在多线程环境下使用。SqlSession 的作用类似于一个 JDBC 中的 Connection 对象,代表着一个连接资源的启用。

SqlSession 作用:

  • 获取 Mapper 接口。
  • 发送 SQL 给数据库。
  • 控制数据库事务。

创建 SqlSession.class

SqlSession sqlSession = SqlSessionFactory.openSession();

SqlSession 只是一个门面接口,它有很多方法,可以直接发送 SQL,而真正实施者是 Executor 。

# SqlSession 事务控制

// 定义 SqlSession
SqlSession sqlSession = null;
try {
    // 打开 SqlSession 会话
    sqlSession = SqlSessionFactory.openSession();
    // some code...
    sqlSession.commit();    // 提交事务
} catch (IOException e) {
    sqlSession.rollback();  // 回滚事务
} finally {
    // 在 finally 语句中确保资源被顺利关闭
    if(sqlSession != null){
        sqlSession.close();
    }
}

# MyBatis 映射器

映射器是 MyBatis 中最重要、最复杂的组件,它由一个接口和对应的 XML 文件(或注解)组成。它可以配置以下内容:

  • 描述映射规则。
  • 提供 SQL 语句,并可以配置 SQL 参数类型、返回类型、缓存刷新等信息。
  • 配置缓存。
  • 提供动态 SQL。

映射器的主要作用就是将 SQL 查询到的结果映射为一个 POJO,或者将 POJO 的数据插入到数据库中,并定义一些关于缓存等的重要内容。

# 使用 xml 实现映射器

用 XML 定义映射器分为两个部分:接口和 XML。

编写映射接口 RoleMapper.java

我们只需要编写接口类,mybatis 会利用动态代理帮我们生成了其实现类。

package com.xfc.mapper;
import com.xfc.entity.Role;
public interface RoleMapper {
    public Role getRole(Long id);
}

使用 xml 方式创建映射器 RoleMapper.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.mybatis.mapper.RoleMapper">
    <!-- id 对应接口内的方法,parameterType 对应参数类型,resultType 对应返回值类型 -->
    <select id="getRole" parameterType="long" resultType="role">
        SELECT id,role_name as roleName,note FROM role WHERE id = #{id}
    </select>
</mapper>

mybatis-config.xml 中配置映射关系:

<mapper resource="com/xfc/mapper/RoleMapper.xml"/>

# 使用注解实现映射器

使用注解实现映射器较为简单,它只需要编写映射接口 RoleMapper.java 即可:

package com.xfc.mapper;
import org.apache.ibatis.annotations.Select;
import com.xfc.entity.User;
public interface UserMapper {
    
    @Select("select id, user_name as userName, password from t_user where id= #{id}")
    public Role getUser(Long id);
    
}

但使用注解实现映射器处理较为复杂的 SQL 时,代码相对复杂,也难以使用动态 SQL,难以维护。

# SqlSession 和 Mapper 接口执行 SQL

MyBatis 执行 SQL 的两种方式:SqlSession 和 Mapper 接口。

# SqlSession 发送 SQL

Role role = (Role)sqlSession.select("com.xfc.mapper.RoleMapper.getRole", 1L);
// 当 mybatis 只有一个 id 为 “getRole”,可以简写如下,selectOne 用于查询返回一条数据
// Role role = (Role)sqlSession.selectOne("getRole", 1L);

# Mapper 接口发送 SQL

RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
Role role = roleMapper.getRole(1L);

推荐使用 Mapper 接口发送 SQL,它可以消除 SqlSession 带来的功能性代码,提高可读性。

# MyBatis 核心组件的作用域及其生命周期

  • SqlSessionFactoryBuilder

    SqlSessionFactoryBuilder 的作用在于创建 SqlSessionFactory,创建成功后,SqlSessionFactoryBuilder 就失去了作用,所以它只能存在于创建 SqlSessionFactory 的方法中,而不要让其长期存在。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。

  • SqlSessionFactory

    SqlSessionFactory 可以被认为是一个数据库连接池,它的作用是创建 SqlSession 接口对象。因为 MyBatis 的本质就是 Java 对数据库的操作,所以 SqlSessionFactory 的生命周期存在于整个 MyBatis 的应用之中,所以一旦创建了 SqlSessionFactory,就要长期保存它,直至不再使用 MyBatis 应用,所以可以认为 SqlSessionFactory 的生命周期就等同于 MyBatis 的应用周期。

    由于 SqlSessionFactory 是一个对数据库的连接池,所以它占据着数据库的连接资源。如果创建多个 SqlSessionFactory,那么就存在多个数据库连接池,这样不利于对数据库资源的控制,也会导致数据库连接资源被消耗光,出现系统宕机等情况,所以尽量避免发生这样的情况。因此在一般的应用中我们往往希望 SqlSessionFactory 作为一个单例,让它在应用中被共享。所以说 SqlSessionFactory 的最佳作用域是应用作用域。

  • SqlSession

    SqlSession 应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给 SqlSessionFactory,否则数据库资源就很快被耗费精光,系统就会瘫痪,所以用 try...catch...finally... 语句来保证其正确关闭,所以 SqlSession 的最佳的作用域是请求或方法作用域。

  • Mapper

    Mapper 是一个接口,它由 SqlSession 所创建,所以它的最大生命周期至多和 SqlSession 保持一致,尽管它很好用,但是由于 SqlSession 的关闭,它的数据库连接资源也会消失,所以它的生命周期应该小于等于 SqlSession 的生命周期。Mapper 代表的是一个请求中的业务处理,所以它应该在一个请求中,一旦处理完了相关的业务,就应该废弃它。

# MyBatis 程序示例

准备

  1. 创建数据库 mybatis ,创建表 user

    CREATE TABLE `user` (
      `uid` tinyint(2)  NOT NULL AUTO_INCREMENT,
      `uname` varchar(20) DEFAULT NULL,
      `usex` varchar(10) DEFAULT NULL,
      PRIMARY KEY (`uid`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  2. 创建 maven 项目并配置 pom.xml

    <?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.xfc</groupId>
        <artifactId>mybatis</artifactId>
        <version>1.0-SNAPSHOT</version>
        <dependencies>
            <!-- https://mvnrepository.com/artifact/log4j/log4j -->
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.17</version>
            </dependency>
            <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
                <version>1.7.25</version>
                <scope>test</scope>
            </dependency>
            <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.13</version>
            </dependency>
            <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.5</version>
            </dependency>
        </dependencies>
    </project>
  3. 配置 JDK 环境(假定已配置完成)。

代码

MyUser.java

package com.xfc.entity;
public class MyUser {
    private Integer uid; // 主键
    private String uname;
    private String usex;
    
    // setter and getter
    @Override
    public String toString() { // 为了方便查看结果,重写了 toString 方法
        return "User[uid=" + uid + ",uname=" + uname + ",usex=" + usex + "]";
    }
}

UserMapper.java

package com.xfc.mapper;
import com.xfc.entity.MyUser;
import java.util.List;
public interface UserMapper {
    MyUser selectUserById(Integer uid);
    List<MyUser> selectAllUser();
    int addUser(MyUser myUser);
    int updateUser(MyUser myUser);
    int deleteUser(Integer uid);
}

log4j.properties

# Global logging configuration
log4j.rootLogger=ERROR,stdout
# MyBatis logging configuration...
log4j.logger.com.mybatis=DEBUG
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

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>
    <settings>
        <setting name="logImpl" value="LOG4J" />
    </settings>
    <!-- 配置 mybatis 运行环境 -->
    <environments default="development">
        <environment id="development">
            <!-- 使用 JDBC 的事务管理 -->
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <!-- MySQL 数据库驱动 -->
                <property name="driver" value="com.mysql.cj.jdbc.Driver" />
                <!-- 连接数据库的 URL -->
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai" />
                <property name="username" value="root" />
                <property name="password" value="root" />
            </dataSource>
        </environment>
    </environments>
    <!-- 将 mapper 文件加入到配置文件中 -->
    <mappers>
        <mapper resource="com/xfc/mapper/UserMapper.xml" />
    </mappers>
</configuration>

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.xfc.mapper.UserMapper">
    <!-- 根据 uid 查询一个用户信息 -->
    <select id="selectUserById" parameterType="Integer" resultType="com.xfc.entity.MyUser">
        select * from user where uid = #{uid}
    </select>
    <!-- 查询所有用户信息 -->
    <select id="selectAllUser" resultType="com.xfc.entity.MyUser">
        select * from user
    </select>
    <!-- 添加一个用户,#{uname} 为 com.xfc.entity.MyUser 的属性值 -->
    <insert id="addUser" parameterType="com.xfc.entity.MyUser">
        insert into user (uname, usex) values(#{uname}, #{usex})
    </insert>
    <!-- 修改一个用户 -->
    <update id="updateUser" parameterType="com.xfc.entity.MyUser">
        update user set uname = #{uname}, usex = #{usex} where uid = #{uid}
    </update>
    <!-- 删除一个用户 -->
    <delete id="deleteUser" parameterType="Integer">
        delete from user where uid = #{uid}
    </delete>
</mapper>

MyBatisTest.java

package com.xfc.test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import com.xfc.entity.MyUser;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class MyBatisTest {
    public static void main(String[] args) {
        try {
            // 读取配置文件 mybatis-config.xml
            InputStream config = Resources.getResourceAsStream("mybatis-config.xml");
            // 根据配置文件构建 SqlSessionFactory
            SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(config);
            // 通过 SqlSessionFactory 创建 SqlSession
            SqlSession ss = ssf.openSession();
            // SqlSession 执行映射文件中定义的 SQL,并返回映射结果
            /*
             * com.xfc.mapper.UserMapper.selectUserById 为 UserMapper.xml 中的命名空间 + select 的 id
             */
            // 查询一个用户
            MyUser mu = ss.selectOne("com.xfc.mapper.UserMapper.selectUserById", 1);
            System.out.println(mu);
            // 添加一个用户
            MyUser addmu = new MyUser();
            addmu.setUname("陈恒");
            addmu.setUsex("男");
            ss.insert("com.xfc.mapper.UserMapper.addUser", addmu);
            // 修改一个用户
            MyUser updatemu = new MyUser();
            updatemu.setUid(1);
            updatemu.setUname("张三");
            updatemu.setUsex("女");
            ss.update("com.xfc.mapper.UserMapper.updateUser", updatemu);
            // 删除一个用户
            ss.delete("com.xfc.mapper.UserMapper.deleteUser", 3);
            // 查询所有用户
            List<MyUser> listMu = ss.selectList("com.xfc.mapper.UserMapper.selectAllUser");
            for (MyUser myUser : listMu) {
                System.out.println(myUser);
            }
            // 提交事务
            ss.commit();
            // 关闭 SqlSession
            ss.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

# 配置文件详解

<?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><!-- 配置 -->
    <properties /><!-- 属性 -->
    <settings /><!-- 设置 -->
    <typeAliases /><!-- 类型命名 -->
    <typeHandlers /><!-- 类型处理器 -->
    <objectFactory /><!-- 对象工厂 -->
    <plugins /><!-- 插件 -->
    <environments><!-- 配置环境 -->
        <environment><!-- 环境变量 -->
            <transactionManager /><!-- 事务管理器 -->
            <dataSource /><!-- 数据源 -->
        </environment>
    </environments>
    <databaseIdProvider /><!-- 数据库厂商标识 -->
    <mappers /><!-- 映射器 -->
</configuration>

注:MyBatis 配置项的顺序不能颠倒,如果颠倒了它们的顺序,那么在 MyBatis 启动阶段就会发生异常,导致程序无法运行。

# MyBatis 核心配置文件 properties

properties 属性可以给系统配置一些运行参数,可以放在 XML 文件或者 properties 文件中,而不是放在 Java 编码中,这样的好处在于方便参数修改,而不会引起代码的重新编译。一般而言,MyBatis 提供了 3 种方式让我们使用 properties,它们是:

  • property 子元素。
  • properties 文件。
  • 程序代码传递。

# property 子元素

如上一示例中,可以在 mybatis-cofig.xml 中,使用 <properties> 下的 <property> 标签定义数据库等参数。

# properties 文件

也可以通过 [jdbc].properties 文件进行配置。

database.driver=com.mysql.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/mybatis
database.username=root
database.password=password

然后在 mybatis-config.xml 中引用该配置即可。

<properties resource="jdbc.properties"/>

此外,也可以按 ${database.username} 的方法引入 properties 文件的属性参数到 MyBatis 配置文件中。

# 使用程序传递方式传递参数

在某些业务场景下,数据库密码需要进行加密,对于这种场景,可以选择使用程序传递方式传递参数。

String resource = "mybatis-config.xml";
InputStream inputStream;
Inputstream in = Resources.getResourceAsStream("jdbc.properties");
Properties props = new Properties();
props.load(in);
String username = props.getProperty("database.username");
String password = props.getProperty("database.password");
// 解密用户和密码,并在属性中重置(假定 CodeUtils 为解密工具类)
props.put("database.username", CodeUtils.decode(username));
props.put ("database.password", CodeUtils.decode(password)); 
inputstream = Resources.getResourceAsStream(resource);
// 使用程序传递的方式覆盖原有的 properties 属性参数
SqlSessionFactory = new SqlSessionFactoryBuilder().build(inputstream, props);

# settings 属性配置

settings 配置项较多,可以在使用时再查阅文档。以下是一个全量配置样例:

<settings>
    <setting name="cacheEnabled" value="true"/>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="multipleResultSetsEnabled" value="true"/>
    <setting name="useColumnLabel" value="true"/>
    <setting name="useGeneratedKeys" value="false"/>
    <setting name="autoMappingBehavior" value="PARTIAL"/>
    <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
    <setting name="defaultExecutorType" value="SIMPLE"/>
    <setting name="defaultStatementTimeout" value="25"/>
    <setting name="defaultFetchSize" value="100"/>
    <setting name="safeRowBoundsEnabled" value="false"/>
    <setting name="mapUnderscoreToCamelCase" value="false"/>
    <setting name="localCacheScope" value="SESSION"/>
    <setting name="jdbcTypeForNull" value="OTHER"/>
    <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

# typeAliases(别名)配置

在 MyBatis 中允许定义一个简写来代表这个类,这就是别名,别名分为系统定义别名和自定义别名。别名由类 TypeAliasRegistry(org.apache.ibatis.type.TypeAliasRegistry)去定义。

# 系统定义别名

别名Java 类型是否支持数组
_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator
ResultSetResultSet

通过代码来实现注册别名

public TypeAliasRegistry() {
    registerAlias("string", String.class);
    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    //...... 省略部分内容
    registerAlias("byte[]",Byte[].class); registerAlias("long[]",Long[].class);
    //...... 省略部分内容
    registerAlias("map", Map.class);
    registerAlias("hashmap", HashMap.class);
    registerAlias("list", List.class); registerAlias("arraylist", ArrayList.class);
    registerAlias("collection", Collection.class);
    registerAlias("iterator", Iterator.class);
    registerAlias("ResultSet", ResultSet.class);
}

一般是通过 Configuration 获取 TypeAliasRegistry 类对象,而事实上 Configuration 对象也对一些常用的配置项配置了别名,如下所示。

// 事务方式别名
typeAliasRegistry.registerAlias("JDBC",JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED",ManagedTransactionFactory.class);
// 数据源类型别名
typeAliasRegistry.registerAlias("JNDI",JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED",
PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED",UnpooledDataSourceFactory.class);
// 缓存策略别名
typeAliasRegistry.registerAlias("PERPETUAL",PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO",FifoCache.class);
typeAliasRegistry.registerAlias("LRU",LruCache.class); typeAliasRegistry.registerAlias("SOFT", SoftCache.class); typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
// 数据库标识别名
typeAliasRegistry.registerAlias("DB_VENDOR",
VendorDatabaseIdProvider.class);
// 语言驱动类别名
typeAliasRegistry.registerAlias("XML",XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW",RawLanguageDriver.class);
// 日志类别名
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGTNG",JakartmCommonsLogginglmpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING",NoLoggingImpl.class);
// 动态代理别名
typeAliasRegistry.registerAlias("CGLIB",CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST",JavassistProxyFactory.class);

# 自定义别名

我们可以通过 TypeAliasRegistry 类的 registerAlias 方法注册,也可以采用配置文件或者扫描方式来自定义别名。

使用配置文件定义别名:

<typeAliases><!-- 别名 -->
    <typeAlias alias="role" type="com.mybatis.po.Role"/>
    <typeAlias alias="user" type="com.mybatis.po.User"/>
</typeAliases>

扫描方式配置别名:

<typeAliases><!-- 别名 -->
    <!--  MyBatis 将扫描这个包里面的类,将其第一个字母变为小写作为其别名 -->
    <package name="com.xfc.entity"/>
</typeAliases>

注解方式配置别名:

package com.xfc.entity;
@Alias("user")
public Class User {
    //...... 省略部分内容
}

# TypeHandler 类型转换器

在 typeHandler 中,分为 jdbcType 和 javaType,其中 jdbcType 用于定义数据库类型,而 javaType 用于定义 Java 类型,那么 typeHandler 的作用就是承担 jdbcType 和 javaType 之间的相互转换。

在很多情况下我们并不需要去配置 typeHandler、jdbcType、javaType,因为 MyBatis 会探测应该使用什么类型的 typeHandler 进行处理,而对于那些需要使用自定义枚举的场景,或者数据库使用特殊数据类型的场景,可以使用自定义的 typeHandler 去处理类型之间的转换问题。

在 MyBatis 中存在系统定义 typeHandler 和自定义 typeHandler。MyBatis 会根据 javaType 和数据库的 jdbcType 来决定采用哪个 typeHandler 处理这些转换规则。

# 系统定义的 TypeHandler

MyBatis 内部定义了许多有用的 typeHandler ,但在大部分的情况下无须显式地声明 jdbcType 和 javaType,或者用 typeHandler 去指定对应的 typeHandler 来实现数据类型转换,因为 MyBatis 系统会自己探测。

要实现 typeHandler 就需要去实现接口 typeHandler,或者继承 BaseTypeHandler 。

# 自定义 TypeHandler

配置 TypeHandler

package com.xfc.test;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.ResultSet;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import org.apache.log4j.Logger;
public class MyTypeHandler implements TypeHandler<String> {
    Logger logger = Logger.getLogger(MyTypeHandler.class);
    @Override
    public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        logger.info("设置 string 参数【" + parameter + "】");
        ps.setString(i, parameter);
    }
    @Override
    public String getResult(ResultSet rs, String columnName) throws SQLException {
        String result = rs.getString(columnName);
        logger.info("读取 string 参数 1 【" + result + "】");
        return result;
    }
    @Override
    public String getResult(ResultSet rs, int columnIndex) throws SQLException {
        String result = rs.getString(columnIndex);
        logger.info("读取string 参数 2【" + result + "】");
        return result;
    }
    @Override
    public String getResult(CallableStatement cs, int columnIndex) throws SQLException {
        String result = cs.getString(columnIndex);
        logger.info("读取 string 参数 3 【" + result + "】");
        return result;
    }
}

启用 TypeHandler

<typeHandlers>
    <typeHandler jdbcType="VARCHAR" javaType="string" handler="com.xfc.test.MyTypeHandler"/>
</typeHandlers>

配置完成后系统才会读取自定义的 TypeHandler ,当 jdbcType 和 javaType 能与自定义 TypeHandler 对应时,它就会启动该 TypeHandler。

<select id="findRoles2" parameterType="string" resultMap="roleMapper">
    select id, role_name, note from t_role where note like concat ('%', #{note, typeHandler=com.mybatis.test.MyTypeHandler},'%')
</select>

要么指定了与自定义 typeHandler 一致的 jdbcType 和 javaType,要么直接使用 typeHandler 指定具体的实现类。

TypeHandler 也可以采用包扫描的形式:

<typeHandlertype>
    <package name="com.xfc.handler"/>
</typeHandlertype>

但采用包扫描的形式时,需要在对应的 TypeHandler 中指定 jdbcType 与 javaType 的对应关系。

@MappedTypes(String.class)
@MappedjdbcTypes(jdbcType.VARCHAR)
public class MyTypeHandler implements TypeHandler<String>{
    //...... 省略部分内容
}

# 枚举 TypeHandler

在绝大多数情况下,typeHandler 因为枚举而使用,MyBatis 已经定义了两个类作为枚举类型的支持,这两个类分别是:

  • EnumOrdinalTypeHandler。
  • EnumTypeHandler。

# BlobTypeHandler 读取 Blob 字段

# ObjectFactory(对象工厂)

当创建结果集时,MyBatis 会使用一个对象工厂来完成创建这个结果集实例。在默认的情况下,MyBatis 会使用其定义的对象工厂 ——DefaultObjectFactory(org.apache.ibatis.reflection.factory.DefaultObjectFactory)来完成对应的工作。

MyBatis 允许注册自定义的 ObjectFactory。如果自定义,则需要实现接口 org.apache.ibatis.reflection.factory.ObjectFactory,并给予配置。

自定义对象工厂

package com.mybatis.test;
import java.util.List;
import java.util.Properties;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.log4j.Logger;
public class MyObjectFactory extends DefaultObjectFactory {
    
    private static final long serialVersionUID = -4293520460481008255L;
    
    Logger log = Logger.getLogger(MyObjectFactory.class);
    private Object temp = null;
    
    @Override
    public void setProperties(Properties properties) {
        super.setProperties(properties);
        log.info("初始化参数:【" + properties.toString() + "】");
    }
    
    // 方法 2
    @Override
    public <T> T create(Class<T> type) {
        T result = super.create(type);
        log.info("创建对象:" + result.toString());
        log.info("是否和上次创建的是同一个对象:【" + (temp == result) + "】");
        return result;
    }
    
    // 方法 1
    @Override
    public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        T result = super.create(type, constructorArgTypes, constructorArgs);
        log.info("创建对象:" + result.toString());
        temp = result;
        return result;
    }
    
    @Override
    public <T> boolean isCollection(Class<T> type) {
        return super.isCollection(type);
    }
}
<objectFactory type="com.xfc.factory.MyObjectFactory">
    <property name="prop1" value="value1" />
</objectFactory>

测试:

package com.xfc.test;
import java.io.IOException;
import java.io.InputStream;
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.apache.log4j.Logger;
import com.xfc.mapper.UserMapper;
import com.xfc.entity.User;
public class MyBatisTest {
    public static void main(String[] args) throws IOException {
        Logger log = Logger.getLogger(MyBatisTest.class);
        InputStream config = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(config);
        SqlSession ss = ssf.openSession();
        UserMapper userMapper = ss.getMapper(UserMapper.class);
        User user = userMapper.getUser(1L);
        System.out.println(user.getUserName());
    }
}

# 参考

  • http://c.biancheng.net/mybatis