目录

1) 简介(特点)

  • ORM(Object Relation Mapping)
  • 半自动:全自动框架如 Hibernate
  • 轻量级:在启动过程中需要的资源较少

2) 项目历史

  • 前身为 Apache 的 iBatis(Internet Abatis)
  • 2010.06 开发团队转投迁移到 Google Code,iBatis3 更名为 MyBatis
  • 2013.11 迁移到 Github

3) Configuration

3.1) SqlMapConfig.xml

MyBatis 的核心配置文件一般命名为 SqlMapConfig.xml,遵循 mybatis-3.x.x.jar!/org/apache/ibatis/builder/xml/mybatis-3-config.dtd 此 DTD 规范,值得一提的是,此 DTD 限制了 XML 的标签顺序如下:

  1. properties
  2. settings
  3. typeAliases
  4. typeHandlers
  5. objectFactory
  6. objectWrapperFactory
  7. reflectorFactory
  8. plugins
  9. environments
  10. databaseIdProvider
  11. mappers

3.1.1) Properties

可用于在子标签中定义一些常量,也可引入一个 prop 文件,还能通过 SqlSessionFactoryBuilder.build() 传入。在 XML 中可以使用 ${key} 引用它们,3.4.2 后支持 ${key:default} 默认值语法,通常用于将 SqlMapConfig 中的 JDBC 配置和其他配置解耦

  • 通过 properties 属性引入 prop:
    • resource:通过项目内资源引入
    • url:通过 url 引入

示例:

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="dev_user"/>
</properties>

3.1.2) Setting

此配置可改变 MyBatis 在运行时的行为,这里就不复制了,直接上官方文档地址: https://mybatis.org/mybatis-3/configuration.html#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>

3.1.3) TypeAliase

词如其名,给类型起别名,可以解耦并提升配置重用性,调用时不区分大小写

需要提到的是,MyBatis 内置的一些常见别名如下,规律为基本类型前加下划线,引用类型适用原类名即可:
_byte, _long, _short, _int, _integer, _double, _float, _boolean, string, byte, long, short, int, integer, double, float, boolean, date, decimal, bigdecimal, object, map, hashmap, list, arraylist, collection, iterator

可通过子标签,和 (包扫描 + 注解重写) 这两种方式定义别名

子标签定义示例:

<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
</typeAliases>

包扫描定义示例:

<typeAliases>
  <package name="domain.blog"/>
</typeAliases>
@Alias("author2")
public class Author {
    ...
}

3.1.4) TypeHandler

可以通过定义 TypeHandler 来实现 mapper 中入参的类型转换。

例子:mapper 中插入 DATE 数据,转换为 VARCHAR

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;

import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.apache.ibatis.type.TypeHandler;
import org.apache.log4j.Logger;

@MappedJdbcTypes({JdbcType.VARCHAR})  // 对应数据库类型
@MappedTypes({Date.class})            // java数据类型
public class MyDateTypeHandler implements TypeHandler<Date>{

    private Logger logger = Logger.getLogger(MyDateTypeHandler.class);

    // 入库前的类型转换
    @Override
    public void setParameter(PreparedStatement ps, int i, Date parameter,
            JdbcType jdbcType) throws SQLException {
        logger.info("setParameter(PreparedStatement ps, int i, Date parameter,JdbcType jdbcType)....");
        ps.setString(i, String.valueOf(parameter.getTime()));
    }
    // 查询后的数据处理
    @Override
    public Date getResult(ResultSet rs, String columnName) throws SQLException {
        logger.info("getResult(ResultSet rs, String columnName)....");
        return new Date(rs.getLong(columnName));
    }
    @Override
    public Date getResult(ResultSet rs, int columnIndex) throws SQLException {
        logger.info("getResult(ResultSet rs, int columnIndex)....");
        return new Date(rs.getLong(columnIndex));
    }
    @Override
    public Date getResult(CallableStatement cs, int columnIndex)
            throws SQLException {
        logger.info("getResult(CallableStatement cs, int columnIndex)....");
        return cs.getDate(columnIndex);
    }
}
3.1.4.1) HandlingEnum

Handling Enum 也属于 typeHandler 的一部分,用于处理输入输出 mapper 的枚举映射,MyBatis 内置了两种 Handler:EnumTypeHandlerEnumOrdinalTypeHandler
一般开发中如果需要用到,都会使用自定义 Handler, 先简单了解,以后用到再深究下下。
可参考文章:

3.1.5) ObjectFactory

MyBatis 在返回结果时,会构造它的实例,默认使用 ObjectFactory 的实现 DefaultObjectFactory 来构造,我们可以通过实现 ObjectFactory 来扩展一些功能。
示例:

<objectFactory type="org.mybatis.example.ExampleObjectFactory"/>

3.1.6) ObjectWrapperFactory

TODO

3.1.7) Plugin(Interceptor)

MyBatis 的插件基于拦截器思想,基于动态代理实现,我们可以对 MyBatis 中以下四大组件中的 method 进行拦截

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)
3.1.7.1) 原理:
  // InterceptorChain.java
  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target); // 执行定义的 plugin 实现动态代理
    }
    return target;
  }

  // Configuratoin.java
  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    // Create instance
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    // 将创建的 instance 交给 Interceptor Chain,执行完 pluginAll 后会返回一个重重(loop)代理对象
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler; // 返回代理对象
  }
3.1.7.2) 介绍:

我们可以通过实现 Interceptor 接口,实现和重写接口中的三个扩展方法:

  1. intercept(Invocation invocation):此接口为主扩展方法,invocation.proceed() 可以继续运行被拦截的方法,使我们可以在前后扩展。
  2. Object plugin(Object target):是的它就是上一节我们分析原理时执行的 plugin 函数,此函数是一个默认方法,可以重写它对创建代理时扩展。
  3. setProperties(Properties properties):在 SqlMapConfig.xml 中声明插件的 plugin 标签定义的参数,会传递到 properties 中。
3.1.7.3) 栗子:

定义:

@Intercepts({@Signature(
        type = Executor.class, method = "update",
        args = {MappedStatement.class, Object.class}
)})
public class ExampleInterceptor implements Interceptor {

  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    /* Before process */ Object result = invocation.proceed(); /* After process */
    return result;
  }
}

声明:

<plugins>
  <plugin interceptor="cat.wars.ExampleInterceptor"></plugin>
</plugins>

3.1.8) Environment

MyBatis 可以定义多个 environment 来实现如 dev、prod 环境数据源切换问题。
但要注意的是,虽然可以配置多个 environment,但每个 SqlSessionFactory 实例只能选择一个。

定义:

<environments default="dev"> <!-- 可以通过修改 default 属性实现数据源简单切换 -->
  <environment id="dev">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <dataSource type="POOLED">
      <property name="driver" value="${driver}"/>
      <property name="url" value="${url}"/>
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>
    </dataSource>
  </environment>
</environments>
3.1.8.1) TransactionManager

MyBatis 的事务有两种类型:

  1. JDBC:直接使用 JDBC 提供的 commit 和 rollback,换句话说使用数据源提供的事务
  2. MANAGED:让容器来接管整个事务,默认会关闭数据库连接,如果不希望这样,可以将 closeConnection 属性设置为 false
    定义:

    <transactionManager type="MANAGED">
    <property name="closeConnection" value="false"/>
    </transactionManager>
3.1.8.2) DataSource

MyBatis 有三种内置数据源类型:

  1. UNPOOLED:不使用连接池,每次操作都打开关闭连接
    1. driver
    2. url
    3. username
    4. password
    5. defaultTransactionIsolationLevel
    6. defaultNetworkTimeout(ms)
    7. driver.encoding
  2. POOLED
    1. 上面列出的属性
    2. poolMaximumActiveConnections
    3. poolMaximumIdleConnections
    4. poolMaximumCheckoutTime
    5. poolTimeToWait
    6. poolMaximumLocalBadConnectionTolerance
    7. poolPingQuery
    8. poolPingEnabled
    9. poolPingConnectionsNotUsedFor
  3. JNDI:这个数据源的实现是为了能在 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文引用
    1. initial_context
    2. data_source
    3. env.encoding=UTF8

3.1.9) DatabaseIdProvider

通过 DatabaseIdProvider 配置可以实现多种 vender 数据源的支持。
原理是从 JDBC 的 DatabaseMetaData#getDatabaseProductName() 元信息中取得 vender name 来实现数据源 vender 的鉴别。
我们可以在 mapper 中的 statement 中加入 databaseId 属性来指定此 statement 优先供应给哪个 vender 使用。

栗子:

<databaseIdProvider type="DB_VENDOR" />

可以通过 property 属性为各 vender 起别名,使得不用考虑 vender 大小写问题:

<databaseIdProvider type="DB_VENDOR">
  <property name="SQL Server" value="sqlserver"/>
  <property name="DB2" value="db2"/>
  <property name="Oracle" value="oracle" />
</databaseIdProvider>

3.1.10) Mapper

最后是 mapper 的配置,用于声明 mapper 的存在 -。-,一共有 4 种声明方式:

  • XML 声明:通过 mapper.xml 中定义的 namespace 找到对应的 class
    1. resource:使用项目中相对路径声明 <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
    2. url:URL 声明 <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  • CLASS 声明:当使用 class 声明时,由于 class 中没有定义 mapper.xml 的存在,所以对应的 mapper.xml 必须和 class 路径保持一致供 MyBatis 使用
    1. class:class 相对路径声明 <mapper class="org.mybatis.builder.AuthorMapper"/>
    2. package:class 所在包扫描声明 <package name="org.mybatis.builder"/>

3.2) mapper.xml

mapper.xml 中的内容太过繁多,赶脚可以用经验代替笔记,暂搁吧,丢官方文档地址:
https://mybatis.org/mybatis-3/sqlmap-xml.html